【操作系统】3.开发工具

目录

[一、Linux 安装软件](#一、Linux 安装软件)

[二、Vim 使用](#二、Vim 使用)

[1. Vim 模式](#1. Vim 模式)

[2. Vim 指令(命令模式)](#2. Vim 指令(命令模式))

[三、gcc/g++ 的使用](#三、gcc/g++ 的使用)

[1. 简单编译+运行](#1. 简单编译+运行)

[2. 指定可执行文件名称](#2. 指定可执行文件名称)

[3. 编译过程](#3. 编译过程)

[4. 静态库和动态库](#4. 静态库和动态库)

[5. 为什么 C 语言要先翻译成汇编,再翻译成机器码?](#5. 为什么 C 语言要先翻译成汇编,再翻译成机器码?)

[6. 如何理解条件编译?](#6. 如何理解条件编译?)

[7. 库的本质](#7. 库的本质)

[四、Make 和 Makefile](#四、Make 和 Makefile)

[1. 简单 Makefile 文件](#1. 简单 Makefile 文件)

[2. .PHONY](#2. .PHONY)

[3. 依赖链](#3. 依赖链)

[4. 变量替换](#4. 变量替换)

[5. 自动变量](#5. 自动变量)

[6. @ 和 echo](#6. @ 和 echo)

[7. 模式规则](#7. 模式规则)

[8. 函数:wildcard](#8. 函数:wildcard)

五、进度条项目

[1. 回车与换行](#1. 回车与换行)

[2. 缓冲区](#2. 缓冲区)

[3. 测试代码](#3. 测试代码)

[4. 进度条程序](#4. 进度条程序)

[六、Git 版本控制器](#六、Git 版本控制器)

基本概念

使用步骤

[七、GDB 调试](#七、GDB 调试)

[1. 编译带调试信息的程序](#1. 编译带调试信息的程序)

[2. 使用 cgdb(更友好的界面)](#2. 使用 cgdb(更友好的界面))

[3. 调试核心](#3. 调试核心)

[4. 常用指令](#4. 常用指令)


一、Linux 安装软件

  • 主要方式:源码安装,软件包安装,包管理器(yum/apt 等)。

  • 由于软件之间有库等依赖关系,因此软件安装是存在依赖的。

  • 通过源码或软件包安装可能会造成包缺失、版本兼容性问题。

  • 而包管理器可以自动解决依赖问题。

  • 因此,安装软件最好使用包管理器。

二、Vim 使用

1. Vim 模式

  • 由于 vim 既可以插入文字,也可以使用不同命令,因此有三种模式:插入模式命令模式底行模式

    • 插入模式:用于写入文本。

    • 命令模式:用于输入编辑指令。

    • 底行模式:用于保存、退出等操作。

  • 模式切换

    • 在命令模式下输入 i 可以进入插入模式写文本,按 Esc 键退出插入模式。

    • 在命令模式下输入 Shift + : 进入底行模式,按 Esc 键退出底行模式。

    • 底行模式输入 set nu 可以显示行号。

    • 底行模式输入 wq 表示写入(保存)并退出。

2. Vim 指令(命令模式)

  1. 光标移动

    • gg:快速移动光标到第一行。

    • Shift + 4 ($):移动光标到行尾。

    • Shift + 6 (^):移动光标到行首。

    • n + Shift + g:将光标移动到第 n 行。

    • h, j, k, l:分别控制光标向左、下、上、右移动(很多命令都支持在前面加上数字,表示重复次数)。

    • b, w:以单词为单位向左、右移动(可以加数字)。

  2. 编辑

    • yy:复制当前行(可加数字表示复制几行)。

    • p:粘贴。

    • u:撤销。

    • Ctrl + r:撤销 u 操作(与 u 互逆)。注意:一旦退出文件就无法撤销,但仅保存文件时操作历史还在,可以撤销。

    • dd:剪切当前行。

    • x:删除光标所在位置的字符。

    • Shift + x:删除光标之前的字符。

    • r:替换单个字符;Shift + r:进入替换模式,批量替换(按 Esc 返回)。

    • Shift + `` (~`):进行大小写转换。

  3. 批量操作

    • Ctrl + v:进入可视块模式(v-block),可以用 h, j, k, l 进行区域选择。

    • Shift + i:在可视块模式下进入插入模式。

    • Shift + 3 (#):高亮当前单词,n 可以逆向查找下一个。

    • 批量加注释示例

      1. Ctrl + v,选择要注释的行区域。

      2. Shift + i 进入插入模式,给第一行加上注释符号(如 //)。

      3. Esc 退出,这样所有选中的行就都加上了注释。​​​​​​​

    • 批量删除注释

      1. 在可视块模式(Ctrl + v)下框住所有注释符号(如 //)。

      2. 直接按 x(删除光标处字符)即可删除。

  4. 底行模式命令

    • !:与 shell 进行交互,! 后跟命令可以在不退出 vim 的情况下执行 shell 命令。

    • wq:保存并退出。

    • %s/ / /:进行替换操作,将前面的内容替换为后面的内容。

  5. 分屏操作

    • 在底行模式下输入 vs + 文件名 可以进行分屏。

    • 光标在哪个窗口就代表在操作哪个窗口。

    • Ctrl + ww:切换到另一个窗口。

  6. 操作技巧

    • ! + 字符:自动执行最近以该字符开头的命令。

    • Ctrl + r:搜索最近使用的命令。

  7. sudo 命令白名单添加

    • root 用户打开 /etc/sudoers 文件。

    • 在用户权限配置部分添加相应的规则。

三、gcc/g++ 的使用

1. 简单编译+运行

复制代码
g++ a.c
./a.out

2. 指定可执行文件名称

复制代码
gcc -o code2 a.c
# 或
gcc a.c -o code1
  • 这样就会生成名为 code2code1 的可执行文件。​​​​​​​

  • 注意-o 选项右边的参数是生成的文件名,顺序不要反。

3. 编译过程

  1. 预处理

    复制代码
    g++ -E a.c -o a.i
    • -E:预处理完就停下。

    • 将预处理后的文件输出到 a.i

    • 可以看到,即使我们只有几行的代码,预处理后也可能变成近千行。​​​​​​​

    • 预处理工作:展开头文件、去除注释、宏替换、条件编译。

    • 生成的 a.i 文件依旧是 C 语言代码。

  2. 编译(生成汇编)

    复制代码
    g++ -S a.i -o a.s
    • -S:开始编译,编译完就停下。
  3. 汇编(生成机器码)

    bash

    复制代码
    g++ -c a.s -o a.o
    • -c:开始汇编,汇编完停下。

    • 生成可重定位文件(.obj,即 .o 文件),是二进制文件。

    • 在项目有多个源文件时,通常会先将所有源文件编译成 .o 文件,再统一链接生成可执行程序。

    • 直接查看 .o 文件是乱码,并且不可执行。

    • 原因 :因为它只是标记了需要哪些库的函数(如 printf),但还不知道这些函数的具体位置(还没有链接)。

    • ldd 命令:查看可执行程序依赖哪些库。​​​​​​​

      • libc 是 C 标准库。
    • 查看其他命令(如 ls)的依赖库:

      bash

      复制代码
      ldd /usr/bin/ls
      • 同样包含 libc 库。
  4. 链接

    • 生成最终的可执行文件或库文件。

4. 静态库和动态库

  • :是一套封装好的方法,为开发提供基本的功能和接口。

  • Linux 库命名规则lib + 库名 + 后缀(.so / .a)。

  • 动态库(.so

    • 程序运行时,当需要调用库函数(如 printf)时,才到库中查找并执行。

    • 因此,程序在链接阶段只需存储库函数的地址信息。

  • 静态库(.a

    • 程序在链接阶段直接将库中用到的函数代码拷贝到可执行文件中。
  • 两者对比

    • 使用动态库生成的可执行程序体积较小。

    • 可执行程序对动态库的依赖较大,不能缺失;而对静态库的依赖小。

    • 静态库链接会导致相同代码在多处重复,造成内存浪费。

5. 为什么 C 语言要先翻译成汇编,再翻译成机器码?

  • 语言发展历史:二进制 -> 汇编 -> C 等高级语言。

  • 在 C 语言发展时,汇编转二进制的技术已经比较成熟,因此可以"站在巨人的肩膀上"。

  • 问题:汇编语言的编译器最初是用什么语言写的?

    • 开始时,由于没有汇编语言,需要有直接用二进制编写的编译器来编译汇编语言的编译器。

    • 接着,再用汇编语言写编译器,并用汇编语言不断迭代完善。

    • 这个过程就叫编译器的自举

6. 如何理解条件编译?

  • 简单条件编译示例

    cpp 复制代码
    #include<stdio.h>
    #define N
    int main() {
    #ifdef N
        printf("%d\n", 1);
    #else
        printf("%d\n", 2);
    #endif
        return 0;
    }
    • 切换输出只需要定义或不定义宏 N 即可。
  • 为什么需要条件编译?

    1. 为软件不同版本(如免费版、付费版)做功能区分。

    2. 内核源码用条件编译进行功能裁剪。

    3. 开发工具等需要适应不同的系统。

  • 也可以在编译命令中定义宏:

    bash

    复制代码
    g++ a.c -o a.out -DN
    • -D 选项后面跟上要定义的宏。

7. 库的本质

  • 假设有两组代码,声明和定义分离在 4 个文件里:

    • code1.h: void func1();

    • code2.h: void func2();

    • code1.c: void func1() { printf("1"); }

    • code2.c: void func2() { printf("2"); }

    • co.c:

      cpp 复制代码
      #include<stdio.h>
      #include"code1.h"
      #include"code2.h"
      int main() {
          func1();
          func2();
          return 0;
      }
  • 要运行 co.c,需要一起编译:g++ co.c code1.c code2.c

  • 如果我们先编译两个函数为 .o 文件:

    复制代码
    g++ -c code1.c -o code1.o
    g++ -c code2.c -o code2.o
  • 当前目录文件结构:

    复制代码
    |-- co.c
    |-- code1.h
    |-- code1.o
    |-- code2.h
    `-- code2.o
  • 然后链接:g++ co.c code1.o code2.o,就可以运行了。

  • 结论 :当多个 .o 文件打包在一起,就形成了一个

  • 因此,编译器为什么要生成 .o 文件?因为它们是链接的基本单位,可以与库进行链接,最终形成可执行文件。

四、Make 和 Makefile

  • make 是命令,Makefile 是文件。

1. 简单 Makefile 文件

makefile

复制代码
code: co.c code1.o code2.o
    g++ -o code co.c code1.o code2.o
  • 其中,code: co.c code1.o code2.o依赖关系

  • g++ -o code co.c code1.o code2.o依赖方法

2. .PHONY

  • 伪目标,让依赖方法总是被执行。

  • 为什么有些命令不会重复执行?

    cpp 复制代码
    test: test.c
        g++ -o test test.c
    • 在这个文件中,目的是编译 test.c

    • 但是连续执行两次 make 命令,会有提示:make: 'test' is up to date.

    • 意思是已经编译过了,相同的文件没必要再编译一次。

    • 只要我们修改一下 test.c 文件,就又可以编译了。

  • 编译器如何判断文件有没有被修改?------ 靠时间戳

    复制代码
    stat test.c
    • 文件有三个时间:Access(访问时间),Modify(内容修改时间),Change(属性修改时间)。

    • Modify:修改文件内容。Change:修改文件属性。修改文件内容时,文件属性(如大小、最后修改时间)也会变,因此 Change 时间会一起变。

    • Access:读取文件。但由于读取很频繁,只有在读取多次后才会更新该时间。

    • 编译器主要依靠 Modify 时间来判断是否需要重新编译。

  • .PHONY 就是忽略时间戳检查,强制执行命令。

    cpp 复制代码
    test: test.c
        g++ -o test test.c
    .PHONY:clean
    clean:
        rm -rf test
    • 由于重复编译没必要,因此 test 目标不加 .PHONY

    • clean 清理操作我们希望每次都执行,因此加上 .PHONY

3. 依赖链

cpp 复制代码
test: test.o
    g++ test.o -o test
test.o: test.s
    g++ -c test.s -o test.o
test.s: test.i
    g++ -S test.i -o test.s
test.i: test.c
    g++ -E test.c -o test.i
.PHONY:clean
clean:
    rm -rf *.i *.s *.o test
  • make 本质上建立了一个栈。

  • 从上往下查找依赖:test: test.o,找不到 .o 文件,就将该命令入栈。同理,test.o: test.stest.s: test.i 依次入栈。

  • 直到 test.i: test.c,找到了 .c 源文件,就执行该命令,然后依次出栈执行。

4. 变量替换

  • 为了方便修改,可以用变量进行替换。

    cpp 复制代码
    BIN=test
    CC=gcc
    SRC=test.c
    FLAGS=-o
    RM=rm -f
    $(BIN):$(SRC)
        $(CC) $(SRC) $(FLAGS) $(BIN)
    .PHONY:clean
    clean:
        $(RM) $(BIN)

5. 自动变量

  • $@:代表目标文件。

  • $^:代表所有依赖文件。

    cpp 复制代码
    $(BIN):$(SRC)
        $(CC) $^ $(FLAGS) $@

6. @echo

  • @ 放在命令前,可以不显示该命令本身。

  • echo 可以输出自定义信息。

    cpp 复制代码
    $(BIN):$(SRC)
        @$(CC) $^ $(FLAGS) $@
        @echo "compile $^ to $@"

7. 模式规则

  • 指定生成规则,例如将所有 .c 文件编译成 .o 文件。

    复制代码
    %.o: %.c
        $(CC) $(FLAGS) $<
    • % 是通配符。

    • $< 代表第一个依赖文件。

8. 函数:wildcard

  • 自动获取相关文件。

    cpp 复制代码
    BIN=test.exe
    CC=gcc
    SRC=$(wildcard *.c)
    OBJ=$(SRC:.c=.o)
    LFLAGS=-o
    FLAGS=-c
    RM=rm -f
    $(BIN):$(OBJ)
        @$(CC) $^ $(LFLAGS) $@
        @echo "compile $^ to $@"
    %.o: %.c
        $(CC) $(FLAGS) $<
    .PHONY:clean
    clean:
        $(RM) $(BIN) $(OBJ)
    .PHONY:print
    print:
        @echo $(BIN)
    • $(wildcard *.c):获取当前目录下所有 .c 文件。

    • $(SRC:.c=.o):将所有 .c 文件名替换为 .o 文件名。

五、进度条项目

1. 回车与换行

  • 回车 (\r):将光标移动到当前行的最左侧。

  • 换行 (\n):将光标移动到下一行。

  • 因此,键盘上的回车键以及 \n 其实是做了"回车+换行"两个动作。

2. 缓冲区

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
int main() {
    printf("hello");
    sleep(3);
    printf("\n");
    printf("hello\n");
    sleep(3);
    return 0;
}
  • 在这个代码中,我们看到的现象是先停了一会,然后连续打印出两个 hello,再停。

  • 原因 :第一个 hello 被打印到输出缓冲区中,但是没有遇到换行符 (\n) 等刷新条件,因此没有立刻显示。直到执行 printf("\n"); 时才刷新缓冲区并显示。

  • 要想立即刷新

    1. 使用 fflush 指令:fflush(stdout);。(stdout 在 Linux 中是显示器的文件描述符)

    2. 使用 fprintffprintfprintf 的区别在于 fprintf 可以指定输出路径(如文件或 stdout)。显示器显示只认字符,输出数字需要先转为字符,因此需要格式化输出。

3. 测试代码

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
int main() {
    int t = 10;
    while (t >= 0) {
        printf("%-2d\r", t);
        fflush(stdout);
        t--;
        sleep(1);
    }
    printf("\n");
    return 0;
}
  • 在屏幕上打印倒计时。

  • 注意点

    1. %-2d\r2d 确保每个数字都占 2 位宽度,防止数字 10 变成 0 时屏幕上留下字符 1- 让数字左对齐,显示更自然。

    2. 最后的 printf("\n");:将最后的数字 0 保留在屏幕上,效果更好看。

4. 进度条程序

  • 头文件 process.h:

    cpp 复制代码
    #define _CRT_SECURE_NO_WARNINGS
    using namespace std;
    #include<stdio.h>
    #include<unistd.h>
    #define LET #
    void setprocess(double cur, double all);
  • 源文件:

    cpp 复制代码
    #define _CRT_SECURE_NO_WARNINGS
    #include "process.h"
    void setprocess(double cur, double all) {
        static int pos = 0;
        static const char* str = "\\-//-\\0";
        int num = (int)(cur * 100 / all);
        string s = string(num, '=') + string(100 - num, ' ');
        const char* ptr = s.c_str();
        printf("[%s][%.2f%%][%c]\r", ptr, 100 * cur / all, str[pos % 3]);
        pos++;
    }
    void download(int all, int spe) {
        int pre = 0;
        while (pre <= all) {
            setprocess(pre, all);
            pre += spe;
            sleep(1);
        }
        printf("%dM finished", all);
    }
    int main() {
        const int req = 1024;
        const int spe = 128;
        download(req, spe);
        return 0;
    }
  • 注意点 :进度条应该是边下载边更新的,因此使用静态变量 pos,每更新一次,旁边的旋转标识就转动一次。

六、Git 版本控制器

基本概念

  1. 仓库:存放项目所有版本文件的文件夹。

  2. 远端仓库:在云服务器(如 GitHub, Gitee)上克隆的本地仓库副本。

  3. Git 是底层的版本控制工具,GitHub/Gitee 是基于 Git 的网站平台。

使用步骤

  1. 创建仓库 :在 GitHub 或 Gitee 上创建新仓库,可以配置 .gitignore 文件来忽略不需要版本控制的文件。

  2. 配置用户名和邮箱(首次使用需要):

    复制代码
    git config --global user.email "your_email@example.com"
    git config --global user.name "Your Name"
  3. 克隆到本地

    复制代码
    git clone + 仓库地址
  4. 添加到暂存区

    复制代码
    git add . # 添加所有未被忽略的文件
  5. 提交到本地仓库

    复制代码
    git commit -m "提交描述信息"
  6. 推送到远端仓库

    复制代码
    git push
  • 这样就完成了一次简单的代码提交。

  • Windows 也支持命令行操作,也可以使用图形化工具如 TortoiseGit 来简化操作。

    • 添加文件 :在资源管理器右键选择 TortoiseGit -> Add。​​​​​​​

    • 提交 :右键选择 Git Commit -> "master"...,填写信息后提交。​​​​​​​

  • 当一个用户更新了远端仓库后,其他用户需要使用 git pull 命令来更新本地仓库,否则无法继续提交。

  • 因此,远端仓库对于所有协作者来说都应该是最新的。

七、GDB 调试

1. 编译带调试信息的程序

复制代码
g++ tiaoshi.cpp -o tiaoshi2 -g
  • 在编译时加上 -g 选项,代表使用 g++ 的 debug 模式,生成调试信息,才能进行调试。

2. 使用 cgdb(更友好的界面)

  • gdb 的界面比较原始,可以使用 cgdb,它将代码和调试信息分开显示。

  • 安装:sudo yum install -y cgdb

3. 调试核心

  • 调试最重要的是找到问题所在。断点本质是将代码块进行级别划分,控制执行流程。

4. 常用指令

  • r (run):运行程序。

  • b + 行号 (break):在指定行添加断点。

  • info b (info breakpoints):查看所有断点及其编号。

  • n (next):相当于 VS 的 F10,逐过程执行(不进入函数内部)。

  • s (step):相当于 VS 的 F11,逐语句执行(会进入函数内部)。

  • gdb 会记录最近的指令,直接按回车可以重复执行上一条指令。

  • finish:执行到当前函数返回。

    • 示例

      cpp 复制代码
      int sum(int l, int r) {
          int ret = 0;
          for (int i = l; i <= r; i++) {
              ret += i;
          }
          return ret;
      }
      int main() {
          int l = 1; int r = 100;
          int ret = sum(l, r);
          cout << ret << endl;
          return 0;
      }
      • sum 函数内部执行 finish 命令后,会停在 int ret = sum(l, r); 这一行。

      • 原因 :这一行其实是两步:调用 sum 函数 + 将返回值赋给变量 retfinish 会执行到函数返回,即寄存器准备好返回值,准备赋值给 ret 的那一步。

  • p + 变量名 (print):打印变量的值。

  • disable / enable + 断点编号:禁用或启用断点。

  • c (continue):继续运行,直到遇到下一个断点。

  • until + 行号:一直运行到指定的行号停止。

  • display + 变量名:一直展示该变量的值(每次停下来都显示)。

  • watch + 变量名:监视变量,当其值发生变化时就显示。

    • 作用:当怀疑某个本不该被修改的常量可能被意外修改时使用。
  • set var + 变量=值:手动将代码中某个变量的值改为指定值。

    • 作用:用于确定程序错误是否由该变量引起。
  • b 行号 if 条件:设置条件断点。

  • condition 断点编号 条件:为已有断点新增或修改条件。

相关推荐
一只小H呀の2 小时前
pandas处理excel数据
excel·pandas
wtsolutions5 小时前
MCP Service Integration - Excel to JSON for AI and Automation
人工智能·json·excel
wtsolutions15 小时前
JSON to Excel Add-in - Seamless Integration Within Excel
json·excel
wtsolutions16 小时前
Getting Started with JSON to Excel Web App - Convert in Seconds
json·excel·web app
wtsolutions19 小时前
Using the JSON to Excel API - Programmatic Access for Developers
json·excel
qq_4351395720 小时前
EasyExcel(FastExcel)Excel导出功能 技术文档
excel
wtsolutions1 天前
Understanding JSON Formats - What JSON to Excel Supports
json·excel
wtsolutions1 天前
Advanced Features - Unlocking the Power of JSON to Excel Pro
linux·json·excel
fs哆哆1 天前
VB.NET和VBA教程-如何查找Excel数据区域边界
excel