云计算百科
云计算领域专业知识百科平台

Linux系列(05)——基础开发工具(下)makefile、git、gdb

⭐️博主: 此生决int-@CSDN博客

 速胜派就是最大的投降派!!!

         🔥热门专栏🔥

      深入理解 C++ 系列 | 算法系列

      快速复习系列 | Linux系列

Gitee地址:财哥 | GitHub地址:财哥


文章目录

    • 上期回顾
    • 4. 自动化构建:`make` / `Makefile`⭐️⭐️⭐️⭐️
      • 4-1 背景
        • 手动编译遇到的问题
        • `make` 和 `Makefile` 的区别
      • 4-2 理解
        • 依赖关系和依赖方法
      • 4-3 基本使用
        • 一个完整的 `Makefile`
        • 规则的书写格式
          • 语法
          • 功能
          • 示例
          • 注意事项
        • 使用 `make`
          • 语法
          • 功能
          • 示例
          • 注意事项
        • `.PHONY` 和伪目标
          • 语法
          • 功能
          • 注意事项
        • 为什么不会重复编译
          • 文件的时间属性
          • 判断规则
      • 4-4 推导过程
        • 像栈一样执行
        • `make` 的工作方式
        • `make` 不检查命令是否正确
      • 4-5 适度扩展语法
        • 变量
          • 语法
          • 示例
        • 自动变量
          • 示例
        • 模式匹配
          • 语法
          • 功能
          • 示例
        • `@`:不回显命令
          • 语法
          • 功能
          • 注意事项
      • 4,总结
        • 一个小而美的Makefile文件⭐️⭐️⭐️⭐️⭐️
      • 核心概念【重点】
      • 规则格式与使用
      • 不重复编译原理【重点】【易忘】
      • 变量与自动变量
    • 5. Linux 第一个系统程序——进度条
      • 5-1 补充:回车与换行
      • 5-2 行缓冲区
        • 5-2-1 为什么调用 `printf` 后不一定马上看见文字
        • 5-2-2 什么是行缓冲
          • 注意事项
      • 5-4 进度条代码
        • 5-4-1 进度条的显示结构
        • 5-4-2 version1:固定 101 格的原理版本
          • 解决的问题
          • 完整代码
        • 5-4-3 version2:根据真实进度更新
          • 为什么需要真实进度
          • 完整代码
        • 5-4-4 配套 Makefile:自动化构建多个源文件
          • 完整 Makefile
      • 总结:5. 第一个系统程序:进度条
        • 回车 vs 换行【易混】【重点】
        • 行缓冲区【重点】
        • 动态显示核心组合【重点】
        • 进度条实现要点
    • 6. 版本控制器 Git
        • Git、本地仓库和远端仓库
      • 6-2 Git 简史
      • 6-3 安装 Git
      • 6-4 在 GitHub 创建项目
        • 创建远端仓库
        • 使用 `git clone` 下载项目
      • 6-5 Git 三板斧
        • 首次使用配置用户名和邮箱
        • 版本管理只管理源文件
        • 使用 `.gitignore` 忽略文件
        • `git push` 被拒绝时的冲突
        • 其他常用命令
    • 总结:6. 版本控制器 Git
      • 核心概念
      • 三板斧【重点】
      • 辅助命令与 .gitignore
    • 7. 调试器 gdb/cgdb
      • cgdb
      • 预备:必须先 -g【重点】
      • gdb命令合集⭐️⭐️⭐️⭐️
      • 常用命令速查
      • 三个调试技巧【重点】
    • 8. 附录:换源实操
      • 步骤主线(两者一致)
      • CentOS 7 换 yum 源
      • Ubuntu 换 apt 源
    • 易混淆对比速查卡
    • 下期预告
    • 线程
    • 结语

上期回顾

  大家好,这里是此生弃int(财哥),欢迎来到我的博客。上一篇我们主要学习了基础开发工具的前半部分,学习和认识了软件包管理器、vim、gcc/g++,那么今天我们继续来学习剩下的几个开发工具:软件包管理器、vim、gcc/g++,那我们开始吧!

4. 自动化构建:make / Makefile⭐️⭐️⭐️⭐️

4-1 背景

手动编译遇到的问题

小程序只有一个源文件时,可以手动执行:

gcc -o myproc myproc.c

gcc 是编译器:把人写的源代码转换成计算机可以运行的程序。

但大型工程可能有成千上万个源文件。每次修改一个文件后,如果都手动输入编译命令,就会遇到两个问题:

  • 需要记住大量编译命令;
  • 没修改的文件也可能被重复编译,浪费时间。
  • 因此,需要一个文件先记录“哪些文件依赖哪些文件”以及“如何生成它们”,再用一个命令自动完成编译。

    make 和 Makefile 的区别
    • make:一个命令,负责读取规则并执行编译。
    • Makefile:一个文件,负责保存编译规则。

    make 是命令,Makefile 是文件;两者搭配完成自动化构建。


    4-2 理解

    依赖关系和依赖方法

    假设要生成一个程序 myproc,它需要源文件 myproc.c。如果没有 myproc.c,就无法生成 myproc,这就是依赖关系:一个文件的生成需要另一个文件。

    但是,仅仅知道依赖谁还不够,还需要知道如何根据 myproc.c 生成 myproc,这就是依赖方法:生成目标时要执行的命令。

    因此,一条规则可以理解为:

    目标文件 ← 依赖文件
    生成目标文件的方法

    Makefile 中的基本形式是:

    目标:依赖
    生成目标的命令

    其中:

    • 目标:想生成或操作的文件;
    • 依赖:生成目标所需要的文件;
    • 命令:生成目标时执行的操作。

    依赖关系决定“先找谁”,依赖方法决定“找到后做什么”。

    核心概念只有两个:依赖关系 + 依赖方法。先有依赖关系,才有执行依赖方法的依据。


    4-3 基本使用

    一个完整的 Makefile

    myproc:myproc.c
    gcc -o myproc myproc.c

    .PHONY:clean
    clean:
    rm -f myproc

    这个文件包含两条主要规则:

    myproc ← myproc.c
    clean ← 无文件依赖

    第一条规则表示:程序 myproc 依赖源文件 myproc.c,生成它时执行:

    gcc -o myproc myproc.c

    第二条规则表示:目标 clean 被调用时,执行:

    rm -f myproc

    rm 是删除文件的命令,-f 表示即使文件不存在,也尽量不要报错。

    规则的书写格式
    语法

    目标:依赖
    命令

    功能
    • 目标:依赖:声明目标和依赖之间的关系;
    • 下一行的命令:说明如何生成或处理目标;
    • 目标和依赖必须顶格写;
    • 命令前必须使用一个真正的 Tab,不能用空格代替。
    示例

    myproc:myproc.c
    gcc -o myproc myproc.c

    注意事项

    下面的缩进必须是 Tab:

    myproc:myproc.c
    gcc -o myproc myproc.c

    如果把 gcc 前面的 Tab 换成空格,make 可能报错,因为 make 用缩进区分“规则声明”和“要执行的命令”。

    使用 make
    语法

    make [目标]

    功能
    • 直接执行 make:执行 Makefile 中的第一个目标;
    • 执行 make clean:明确要求执行名为 clean 的目标。
    示例

    在包含 Makefile 和 myproc.c 的目录中执行:

    make

    等价于让 make 根据第一条规则决定是否执行:

    gcc -o myproc myproc.c

    执行清理操作:

    make clean

    这会执行:

    rm -f myproc

    注意事项

    clean 不会因为写在 Makefile 中就自动执行。直接执行 make 时,默认只处理第一个目标,也就是 myproc。

    .PHONY 和伪目标
    语法

    .PHONY:目标

    功能

    .PHONY 用来声明一个伪目标。伪目标是“名字看起来像目标,但不一定对应真实文件”的目标。

    例如:

    .PHONY:clean #完成清理任务,而不是生成一个clear文件
    clean:
    rm -f myproc

    这里的 clean 通常不是一个真实文件,而是一个“执行清理命令”的名称。

    注意事项

    如果当前目录中恰好存在一个名为 clean 的文件,没有声明 .PHONY 时,make clean 可能认为目标已经完成,从而不执行命令。

    声明后:

    .PHONY:clean

    make 会忽略同名文件的影响,每次执行:

    .PHONY 的作用:让 make 忽略源文件和目标文件的时间对比,把目标当成每次都需要执行的伪目标。

    为什么不会重复编译

    make 会比较目标文件和依赖文件的修改时间。

    文件的时间属性

    文件不仅包含内容,还包含属性。常见的时间属性有:

    属性含义
    Modify 文件内容最后一次被修改的时间
    Change 文件属性最后一次发生变化的时间
    Access 文件最后一次被访问的时间

    在判断是否需要重新构建时,make 主要比较文件的 Modify 时间。

    判断规则
    • myproc 不存在:执行命令;
    • myproc.c 的**修改时间(Modify)**比 myproc 新:执行命令;
    • myproc.c 没有比 myproc 新:通常不执行命令。

    因为依赖文件更新后,目标文件可能已经过时,所以需要重新生成。

    make 默认不重复编译,是因为它比较目标文件和依赖文件的 Modify 时间;只有目标不存在或依赖更新得更晚时,才重新执行命令。


    4-4 推导过程

    myproc 依赖 myproc.o
    myproc.o 依赖 myproc.s
    myproc.s 依赖 myproc.i
    myproc.i 依赖 myproc.c

    像栈一样执行

    栈是一种“后进先出”的结构:最后放进去的内容最先取出来。

    make 处理依赖链时,先沿着依赖关系向下查找,直到找到最底层的源文件;然后再从底层向上逐层生成目标:

    向下查找:myproc → myproc.o → myproc.s → myproc.i → myproc.c
    向上生成:myproc.c → myproc.i → myproc.s → myproc.o → myproc

    因此,依赖关系像入栈,生成过程像出栈。

    make 先沿依赖链向下寻找最底层文件,再从底层向上逐层执行命令。

    make 的工作方式

    具体来说:

  • make 先寻找 Makefile;
  • 直接执行 make 时,把第一个目标作为最终目标;
  • 如果目标不存在,执行它的命令;
  • 如果目标存在,但依赖文件比目标更新,执行它的命令;
  • 如果依赖文件不存在,继续查找“如何生成这个依赖”的规则;
  • 如果既找不到依赖文件,也找不到生成它的规则,就报错退出。
  • make 不检查命令是否正确

    make 只负责根据文件的依赖关系决定“是否执行命令”,不负责判断命令本身是否正确。


    4-5 适度扩展语法

    变量
    语法

    变量名=值

    使用变量时写成:

    $(变量名)

    变量就是一个可以反复使用的名字,用来保存文件名、命令名或文件列表,避免同样的内容重复书写。作用类比定义宏

    示例

    BIN=proc.exe
    CC=gcc
    SRC=myproc.c
    OBJ=myproc.o
    RM=rm -f

    # 使用时加$()
    $(BIN):$(SRC)
    $(CC) -o $(BIN) $(SRC)

    .PHONY:clear
    clear:
    $(RM) $(OBJ) $(BIN)

    自动变量

    自动变量只能在规则的命令中使用,make 会根据当前规则自动为它们填入内容。

    自动变量含义
    $@ 当前目标文件
    $^ 当前规则的全部依赖
    $< 当前规则的第一个依赖
    示例

    myproc:main.o print.o
    gcc -o $@ $^

    # $@ → myproc
    # $^ → main.o print.o
    # 因此等价于:
    gcc -o myproc main.o print.o

    模式匹配
    语法

    %.o:%.c
    命令

    功能

    % 表示一个可以变化的部分。%.o:%.c 表示:

    • 任意一个 .o 文件;
    • 依赖同名的 .c 文件。

    例如:

    main.o ← main.c
    print.o ← print.c

    都可以匹配这条规则。

    示例

    %.o:%.c
    $(CC) -c $< -o $@

    当处理 main.o 时:

    $< → main.c
    $@ → main.o

    命令等价于:

    gcc -c main.c -o main.o

    @:不回显命令
    语法

    目标:
    @命令

    功能

    默认情况下,make 执行命令前,通常会先把命令显示在屏幕上。在命令前加 @,可以只执行命令而不显示这行命令。

    注意事项

    @ 只影响命令是否回显,不改变命令本身的功能。

    4,总结

    一个小而美的Makefile文件⭐️⭐️⭐️⭐️⭐️

    BIN=proc.exe
    CC=gcc
    #SRC=$(shell ls *.c) #执行shell命令,列出所有.c文件
    SRC=$(wildcard *.c) # makefile命令,列出所有.c文件
    OBJ=$(SRC:.c=.o) # 把SRC里面的所有.c文件换成.o
    LFLAGS=-o
    FLAGS=-c
    RM=rm -f

    $(BIN):$(OBJ)
    @$(CC) $(LFLAGS) $@ $^ # $^ 当前所有的依赖文件,$@ 当前目标文件,即上面的BIN
    @echo "linking … $^ to $@"

    %.o:%.c
    @$(CC) $(FLAGS) $< # $< 当前的第一个依赖
    @echo "compiling … $< to $@"

    .PHONY:clean
    clean:
    $(RM) $(OBJ) $(BIN)

    .PHONY:test
    test:
    @echo $(SRC)
    @echo $(OBJ)


    效果: 在这里插入图片描述

    核心概念【重点】

    • make 是命令(执行者),Makefile 是文件(规则说明书)。
    • 规则两要素:依赖关系(先找谁)+ 依赖方法(找到后做什么)。

    规则格式与使用

    myproc:myproc.c # 目标:依赖(顶格写)
    gcc -o myproc myproc.c # 命令前必须是一个真正的 Tab,不能用空格【易错】

    .PHONY:clean # 声明伪目标
    clean: # 无依赖
    rm -f myproc

    • make = 执行第一个目标;make clean = 指定执行 clean(clean 不会自动跑)。
    • .PHONY 作用:防止目录里恰好有同名文件 clean 导致 make 误判"已完成"而不执行。【重点】

    不重复编译原理【重点】【易忘】

    • make 比较目标与依赖的 Modify 时间(内容修改时间)。
    • 目标不存在 → 执行;依赖比目标新 → 执行;否则不执行。
    • 文件三时间【易混】:Modify 内容修改 / Change 属性或内容变化 / Access 访问。
    • 推导链像栈:先沿依赖向下找到最底层源文件,再从底向上逐层生成(入栈查找、出栈生成)。
    • make 只管"是否执行命令",不管命令本身对错(gcc 没装、代码有错它都不管)。

    变量与自动变量

    BIN=proc.exe # 定义变量,使用时 $(BIN)
    SRC=$(wildcard *.c) # 找出当前目录所有 .c
    OBJ=$(SRC:.c=.o) # 后缀替换:a.c b.c → a.o b.o

    【易混】 三个自动变量(只能用在规则的命令里):

    变量含义
    $@ 目标文件
    $^ 全部依赖
    $< 第一个依赖
    • 模式匹配:%.o:%.c = 任意 .o 依赖同名 .c。
    • 命令前加 @ = 执行但不回显该命令。【易忘】

    5. Linux 第一个系统程序——进度条

    进度条看起来只是不断改变一行文字,但它依赖三个关键知识:

    打印文字
    ↓
    控制光标回到行首
    ↓
    刷新输出缓冲区
    ↓
    同一行被不断覆盖,形成动态效果


    5-1 补充:回车与换行

    写法名称实际动作
    \\r 回车 回到当前行的行首
    \\n 换行 移动到下一行

    \\r 不会换到下一行,它只把光标拉回当前行开头;\\n 也不负责回到行首,它只负责向下移动一行。

    强调:\\r 是回到行首,\\n 是换到下一行,它们是两个动作;键盘回车键通常对应 \\r\\n。


    5-2 行缓冲区

    5-2-1 为什么调用 printf 后不一定马上看见文字

    假设程序打印一段文字后要等待 3 秒:

    #include <stdio.h>
    #include <unistd.h>

    int main(void)
    {
    printf("hello");
    sleep(3);

    return 0;
    }

    有时程序运行后,等待期间看不到 hello,直到程序结束才显示。这不是 printf 没有执行,而是文字暂时没有送到显示器。

    程序通常会先把输出文字放进一块临时内存中,等条件合适时再一次性送出去。这块临时内存叫缓冲区:可以把它理解为“先暂存数据的中转区”。

    5-2-2 什么是行缓冲

    行缓冲:程序暂时保存一行输出,遇到换行符 \\n 时,通常把这一行送到显示设备。下面有三个例子

    #include <stdio.h>
    #include <unistd.h>

    int main(void)
    {
    // ==================== ① printf() 带 \\n ====================
    // \\n 通常会触发 stdout 刷新,因此 hello 通常会立即显示
    printf("hello\\n");
    sleep(3);

    // ==================== ② printf() 不带 \\n ====================
    // 没有换行,hello 可能暂时停留在 stdout 缓冲区
    printf("hello");
    sleep(3);

    // ==================== ③ 使用 fflush(stdout) ====================
    // 手动刷新 stdout,使缓冲区中的 hello 立即输出
    printf("hello");
    fflush(stdout);
    sleep(3);

    return 0;
    }

    注意事项
    • stdout 表示标准输出,通常就是终端显示器。
    • fflush(stdout) 只负责“把内容送出去”,不会自动换行。
    • 动态进度条通常需要同时使用 \\r 和 fflush(stdout)。

    5-4 进度条代码

    5-4-1 进度条的显示结构

    一个简单的进度条可以设计成:

    [######################### ][53%][/]

    其中:

    • 第一部分表示已经完成的进度;
    • [53%] 表示百分比;
    • 最后的 |、/、-、\\ 是旋转光标,用来表示程序仍在工作。

    旋转光标按照下面的顺序变化:

    | → / → – → \\ → |

    注意:反斜杠在 C 字符串中有特殊含义,因此要写成 \\\\。


    5-4-2 version1:固定 101 格的原理版本
    解决的问题

    最开始可以不考虑真实下载量,只模拟从 0% 增长到 100%。为了让进度条逐格增加,准备一个固定长度的字符数组:

    [ ]

    每完成一点,就把其中一个空格改成 #,然后重新输出整条进度条。

    这里使用 100 个位置表示 0% 到 100%,数组还要额外留一个位置存放字符串结束标记 \\0,所以数组大小是 101。

    完整代码

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>

    int main(void)
    {
    // buffer 用来保存进度条
    // 100 个字符用于显示进度,最后 1 个字符用于存放字符串结束符 '\\0'
    char buffer[101];

    // 用于记录当前旋转光标的位置
    int spinner_index = 0;

    // 旋转光标的 4 种状态:| / – \\
    const char spinner[] = "|/-\\\\";

    // 将 buffer 前 100 个位置全部初始化为空格
    // 此时得到的是一条长度为 100 的空进度条
    memset(buffer, ' ', 100);

    // C 字符串必须以 '\\0' 结尾
    // 因此 buffer[100] 不能用于显示进度,必须保存 '\\0'
    buffer[100] = '\\0';

    // i 表示当前进度,从 0% 一直到 100%
    for (int i = 0; i <= 100; i++) {

    // i == 0 时还没有完成任何进度,因此不需要填充 '#'
    // 从 i == 1 开始,每循环一次就将一个空格替换成 '#'
    if (i > 0) {
    buffer[i – 1] = '#';
    }

    // \\r:将光标移动到当前行开头
    // %s:输出进度条字符串
    // %3d:百分比至少占 3 个字符,保证 0~100 的显示宽度一致
    // %%:printf 中输出字面量 '%'
    // %c:输出当前旋转光标字符
    printf("\\r[%s][%3d%%][%c]",
    buffer,
    i,
    spinner[spinner_index]);

    // stdout 默认可能存在缓冲
    // fflush(stdout) 强制立即刷新输出,让进度条实时显示
    fflush(stdout);

    // 旋转光标下标循环:
    // 0 -> 1 -> 2 -> 3 -> 0 -> …
    // 对应:
    // | -> / -> – -> \\ -> | -> …
    spinner_index = (spinner_index + 1) % 4;

    // 暂停 50000 微秒,即 50 毫秒
    // 控制进度条更新速度
    usleep(50000);
    }

    // 进度完成后换行
    printf("\\n");

    return 0;
    }

    5-4-3 version2:根据真实进度更新
    为什么需要真实进度

    模拟程序可以直接用 i 表示百分比,但真实下载时通常知道的是:

    • 总大小 total;
    • 当前已经下载的大小 current。

    例如,总文件大小是 200 MB,已经下载 100 MB:

    百分比 = current / total × 100
    = 100 / 200 × 100
    = 50%

    完整代码

    #include <stdio.h>
    #include <unistd.h>

    // 刷新并显示进度条
    // total:任务总量
    // current:当前已经完成的任务量
    void FlushProcess(int total, int current)
    {
    // 进度条的总宽度为 50 个字符
    const int bar_width = 50;

    // 旋转光标的 4 种状态
    // 注意:\\ 是转义字符,因此需要写成 "\\\\"
    const char spinner[] = "|/-\\\\";

    // static 变量只初始化一次
    // 函数每次调用结束后,spinner_index 的值不会丢失
    // 因此可以在多次调用 FlushProcess() 时不断切换旋转光标
    static int spinner_index = 0;

    // 如果总任务量 <= 0,没有意义,直接返回
    if (total <= 0) {
    return;
    }

    // 防止 current 超过 total
    // 例如 total = 100,current = 120 时,按照 100% 处理
    if (current > total) {
    current = total;
    }

    // 根据当前完成量计算百分比
    // 例如:current = 25,total = 100
    // percent = 25 * 100 / 100 = 25
    int percent = current * 100 / total;

    // 根据当前完成量计算进度条中需要填充多少个 '#'
    // bar_width = 50
    // 例如完成 50%,filled = 25
    int filled = current * bar_width / total;

    // \\r:将光标移动到当前行开头
    // 这样可以覆盖之前显示的进度条,实现动态刷新
    printf("\\r[");

    // 绘制 50 个字符的进度条
    for (int i = 0; i < bar_width; i++) {

    // 如果当前位置小于 filled
    // 说明这个位置已经完成,用 '#' 表示
    if (i < filled) {
    printf("#");
    } else {
    // 否则用空格表示还没有完成
    printf(" ");
    }
    }

    // 输出进度条右边的百分比和旋转光标
    // %3d:百分比至少占 3 个字符,保证 0~100 显示宽度一致
    // %%:printf 中输出字面量 '%'
    // %c:输出一个旋转光标字符
    printf("][%3d%%][%c]",
    percent,
    spinner[spinner_index]);

    // stdout 默认可能存在缓冲
    // fflush() 强制立即刷新输出,使进度条能够实时显示
    fflush(stdout);

    // 更新旋转光标下标
    // 0 -> 1 -> 2 -> 3 -> 0 -> …
    // 对应:
    // | -> / -> – -> \\ -> | -> …
    spinner_index = (spinner_index + 1) % 4;
    }

    int main(void)
    {
    // 设置任务总量为 100
    int total = 100;

    // current 表示当前已经完成的任务量
    // 从 0 开始,一直增加到 total
    for (int current = 0; current <= total; current++) {

    // 根据当前进度刷新进度条
    FlushProcess(total, current);

    // 暂停 50000 微秒,即 50 毫秒
    // 控制进度条的刷新速度
    usleep(50000);
    }

    // 进度完成后换行
    printf("\\n");

    return 0;
    }

    5-4-4 配套 Makefile:自动化构建多个源文件
    完整 Makefile

    BIN=processbar.exe
    SRC=$(wildcard *.c)
    OBJ=$(SRC:.c=.o)
    $(BIN):$(OBJ)
    gcc -o $@ $^
    %.o:%.c
    gcc -c $< -o $@
    .PHONY:clean
    clean:
    rm -f $(OBJ) $(BIN)

    总结:5. 第一个系统程序:进度条

    回车 vs 换行【易混】【重点】
    写法名称动作
    \\r 回车 回到当前行行首(不换行)
    \\n 换行 移到下一行(不回行首)

    键盘回车键 ≈ \\r\\n 两个动作合一(老式打字机:打字头移动 + 纸张移动)。

    行缓冲区【重点】
    • printf 的文字先进缓冲区(中转暂存区),不一定立刻显示。
    • 行缓冲:遇到 \\n 才送出;缓冲区满、程序结束也会触发刷新。
    • 手动刷新:fflush(stdout); —— 只负责送出,不换行。
    • 三个标准流【易忘】:stdin 键盘输入 / stdout 屏幕输出 / stderr 错误输出;终端是字符设备。
    动态显示核心组合【重点】

    printf("%-2d\\r", i); # %–2d:宽度2左对齐(防止 10→9 残留字符);\\r:回行首覆盖
    fflush(stdout); # 立即刷新,否则留在缓冲区看不到变化

    进度条实现要点
    • 结构:[#### ][53%][/] = 已完成 + 百分比 + 旋转光标 |/-\\(C 字符串里反斜杠写 \\\\)。
    • v1:固定 101 格数组(100 格 + \\0),每轮填一个 #,\\r 重绘。
    • v2:percent = current * 100 / total,filled = current * bar_width / total,真实下载每完成一块调一次刷新。
    • %3d 固定百分比宽度,防抖动;%% 输出字面量 %。

    6. 版本控制器 Git

    版本控制器:自动记录文件每次改动和版本变化的软件。它不仅能保存历史版本,还能帮助多人同时开发同一个项目。

    版本控制器主要解决两个问题:

  • 数据安全:文件改错或误删后,可以找回以前的版本。
  • 协作开发:多人修改同一个项目时,可以交换各自的修改,并记录是谁、什么时候改了什么。
  • Git、本地仓库和远端仓库

    如果每个人都把项目文件放在自己电脑上,其他人无法方便地获取最新修改。因此,需要一个专门保存项目历史记录的地方。

    Git:一种版本控制器软件,负责记录文件变化、保存版本并同步修改。

    仓库:保存项目文件和版本历史记录的地方。可以把它理解成“项目文件夹加上完整的修改记录”。

    Git 通常涉及两个仓库:

    名称保存位置作用
    本地仓库 自己的电脑 保存自己提交的版本
    远端仓库 云服务器或其他服务器 让多人共享和备份项目

    Git 既可以作为操作本地仓库的工具,也可以通过网络和其他仓库交换数据,所以它既是 client(客户端:主动连接并操作其他仓库的一方),也能作为 server(服务器端:向其他电脑提供仓库数据的一方)。

    GitHub 和 Gitee 是基于 Git 建立的网站平台。它们提供云端仓库、网页管理和多人协作功能,但它们本身不是 Git 软件。

    Git 是分布式版本控制系统:每个开发者的本地仓库都保存完整的版本历史,不是只能依赖一个中央服务器。即使暂时没有网络,也可以在本地查看历史、创建版本;联网后再与远端仓库同步。

    本地仓库保证本地开发和历史记录,远端仓库用于备份和多人共享。


    6-2 Git 简史

    Linux 内核是一个非常大的开源项目,需要很多人长期共同维护。早期没有 Git,开发者主要依靠手工发送和合并补丁。

    补丁:描述“应该对原文件做哪些修改”的文件。

    发展过程如下:

    1991—2002:主要依靠手工补丁维护 Linux 内核
    ↓
    2002:使用商业版本控制软件 BitKeeper
    ↓
    2005:BitKeeper 不再免费提供
    ↓
    Linus Torvalds 自己开发 Git

    Git 的设计目标是:

    • 速度快:快速处理大量文件和修改记录。
    • 设计简单:核心操作尽量清晰直接。
    • 支持非线性开发:可以让很多开发分支同时进行,再合并结果。
    • 完全分布式:每个本地仓库都拥有完整历史,不完全依赖中央服务器。
    • 适合超大项目:能够高效管理像 Linux 内核这样的巨大工程。

    分支:从项目当前版本分出的一条独立开发线路。例如,一组人开发登录功能,另一组人开发支付功能,可以分别在不同分支中工作。


    6-3 安装 Git

    不同 Linux 系统使用的安装工具不同。

    sudo yum install git
    sudo apt install -y git

    git –version # 验证安装结果

    6-4 在 GitHub 创建项目

    如果想把本地项目保存到云端并与他人协作,需要先在 GitHub 上创建一个远端仓库。

    创建远端仓库

    操作流程:

  • 注册 GitHub 账号。
  • 按要求验证邮箱。
  • 登录后点击 New repository。
  • 输入项目名称并创建项目。
  • 复制项目链接。
  • 项目链接通常类似:

    https://github.com/用户名/项目名.git

    在这里插入图片描述

    仓库链接:远端仓库在网络上的地址,Git 根据这个地址连接仓库。

    使用 git clone 下载项目

    如果远端已经有一个项目,需要先把它复制到本地。

    语法:

    git clone [url] #从远端仓库下载项目文件和版本历史,并在当前目录创建一个本地仓库。
    # 示例
    git clone https://github.com/octocat/Hello-World.git

    执行后,当前目录通常会出现一个 Hello-World 文件夹。

    注意事项:

    • [url] 要替换成自己复制的项目链接。
    • clone 不只是下载当前文件,还会下载 Git 的版本历史。
    • 执行命令前,应先进入希望保存项目的目录。

    6-5 Git 三板斧

    手动修改文件后,Git 不会自动把修改提交到仓库。因为有些修改可能只是临时尝试,Git 需要先让你选择哪些修改要保存。

    Git 的基本流程是:

    # 第一板斧:git add
    git add 文件名 # 将指定文件的修改放入暂存区
    git add . # 将当前目录下的所有变更放入暂存区

    # 第二板斧:git commit
    git commit -m "日志" # 将暂存区内容提交到本地仓库,形成新的版本

    # 第三板斧:git push
    git push # 将本地仓库已提交的版本上传到远程仓库

    暂存区:提交前临时放置“准备保存哪些修改”的区域。

    注意事项:

    • push 只能上传已经 commit 的内容,不能直接上传只执行了 add 的内容。
    • 第一次推送时,可能需要登录 GitHub,或按提示设置远端分支。
    • push 成功后,其他协作者才能从远端获取你的提交。
    首次使用配置用户名和邮箱

    Git 需要知道每次提交是谁完成的,因此第一次使用时要配置用户名和邮箱。

    语法:

    git config –global user.name "用户名"
    git config –global user.email "邮箱"

    **功能:**设置当前用户的提交身份;–global 表示对当前电脑上的所有 Git 项目生效。

    注意事项:

    • 邮箱应填写自己使用的邮箱。
    • 这只是记录提交者身份,不等同于完成 GitHub 登录。

    通常需要配置私人令牌 在这里插入图片描述

    版本管理只管理源文件

    源文件:程序员编写、可以直接阅读和修改的代码文件。例如 .cpp、.h、.py 等。

    编译程序时,通常还会生成可执行文件、临时文件或缓存文件。这些文件可以由程序重新生成,不需要放进版本仓库,因此版本管理主要保存源文件和必要的配置文件。

    版本管理主要管理源文件,不要把可执行文件、临时文件和缓存文件全部提交进去。

    使用 .gitignore 忽略文件

    如果每次执行 git add . 都把临时文件加入暂存区,就需要反复手动删除。因此,可以告诉 Git 哪些文件不要管理。

    .gitignore:列出需要被 Git 忽略的文件名或文件后缀的配置文件。

    在项目根目录创建名为 .gitignore 的文件,例如:

    *.o # 忽略所有以 .o 结尾的文件
    *.out # 忽略所有以 .out 结尾的文件
    build/ # 忽略名为 build 的目录

    注意事项:

    • .gitignore 本身通常应该提交到仓库,让所有协作者使用同一套忽略规则。
    • 已经被 Git 提交过的文件,后来再写入 .gitignore,不会自动从历史中消失;.gitignore 主要防止尚未被管理的文件被加入。

    .gitignore 用来提前排除不应进入版本仓库的文件和目录。

    git push 被拒绝时的冲突

    有时执行 git push 会出现 rejected。这通常表示:远端仓库已经有了本地没有的更新。

    例如:

    远端仓库:A —— B
    本地仓库:A —— C

    因为远端比本地更新,如果直接推送,Git 不知道如何安全地覆盖远端内容,所以拒绝操作。

    解决流程:

    git pull #从远端获取最新内容,并尝试合并到当前本地分支。
    git push

    如果两边修改了同一位置,可能产生冲突。冲突就是 Git 无法自动判断应该保留哪一份修改,需要手动修改文件后,再执行 git add、git commit 和 git push。

    远端比本地新时,先用 git pull 同步,再用 git push 上传。

    其他常用命令

    git status # 查看当前仓库状态:修改、暂存、未跟踪等

    git log # 查看本地仓库的提交历史

    git pull # 获取远程仓库的新提交,并合并到当前分支

    # 基本提交流程
    git status # 提交前检查仓库状态
    git add main.cpp # 将修改放入暂存区
    git commit -m "完成主要功能" # 将暂存区内容提交到本地仓库
    git push # 将本地提交上传到远程仓库

    # 如果 push 被拒绝,通常先同步远程仓库
    git pull # 获取并合并远程的新提交
    git push # 再次推送本地提交


    总结:6. 版本控制器 Git

    核心概念

    • 版本控制器两大价值:数据安全(改错可找回)+ 协作开发(记录谁改了什么)。【重点】
    • 仓库 = 项目文件 + 完整修改记录;本地仓库(自己电脑)vs 远端仓库(云端,共享备份)。
    • Git 是分布式:每个本地仓库都有完整历史,断网也能本地提交。
    • GitHub/Gitee 是基于 Git 的平台,≠ Git 本身。【易混】
    • 首次使用配身份:git config –global user.name "名" + user.email "邮箱"。

    三板斧【重点】

    git add main.cpp # ① 修改 → 暂存区(git add . 暂存全部,慎用)
    git commit -m "修复登录错误" # ② 暂存区 → 本地仓库(-m 写清做了什么;只提交变化部分)
    git push # ③ 本地仓库 → 远端仓库

    流程记忆:工作目录 →(add)→ 暂存区 →(commit)→ 本地仓库 →(push)→ 远端仓库。 push 只能传 commit 过的内容;只 add 没 commit 的内容传不上去。

    辅助命令与 .gitignore

    • git status(add/commit 前查状态)、git log(看提交历史)、git pull(拉远端并合并)。
    • push 被拒绝(rejected)= 远端比本地新 → 先 git pull 同步,再 git push;同一位置两边都改 = 冲突,手动改完后重新 add/commit/push。【重点】
    • .gitignore:列出要忽略的文件/后缀/目录(*.o、build/);只管理源文件,可执行文件/临时文件不进仓库。
    • .gitignore 本身应提交;已被管理过的文件再加入 .gitignore 不会从历史消失。

    7. 调试器 gdb/cgdb

    cgdb

    建议使用cgdb,两者命令完全不变

    • 分屏可视化版 gdb,命令完全不变;yum/apt install -y cgdb 安装;ESC 进代码屏,i 回命令屏。

    预备:必须先 -g【重点】

    • debug(测试用,带调试信息)vs release(上线用,无调试信息)。
    • gcc/g++ 默认编译 release,无法源码级调试 → 必须 gcc -g mycmd.c -o mycmd。
    • 验证:file mycmd 看 with debug_info;或 readelf -S mycmd | grep -i debug 看 .debug_info 段。

    在这里插入图片描述 在这里插入图片描述

    • 开始:gdb binFile
    • 退出:ctrl + d 或 quit

    gdb命令合集⭐️⭐️⭐️⭐️

    调试命令作用样例
    list/l 显示源代码,从上次位置开始,每次列出 10 行 list/l 10
    list/l 函数名 列出指定函数的源代码 list/l main
    list/l 文件名:行号 列出指定文件的源代码 list/l mycmd.c:1
    r/run 从程序开始连续执行 run
    n/next 单步执行,不进入函数内部,逐过程 F10 next
    s/step 单步执行,进入函数内部,逐语句 F11 step
    break/b [文件名:]行号 在指定行号设置断点 break 10break test.c:10
    break/b 函数名 在函数开头设置断点 break main
    info break/b 查看当前所有断点的信息 info break
    finish 执行到当前函数返回,然后停止 finish
    print/p 表达式 打印表达式的值 print start+end
    p 变量 打印指定变量的值 p x
    set var 变量=值 修改变量的值 set var i=10
    continue/c 从当前位置开始连续执行程序 continue
    delete/d breakpoints 删除所有断点 delete breakpoints
    delete/d breakpoints n 删除序号为 n 的断点 delete breakpoints 1
    disable breakpoints 禁用所有断点 disable breakpoints
    enable breakpoints 启用所有断点 enable breakpoints
    info/i breakpoints 查看当前设置的断点列表 info breakpoints
    display 变量名 跟踪显示指定变量的值(每次停止时) display x
    undisplay 编号 取消对指定编号的变量的跟踪显示 undisplay 1
    until X 行号 执行到指定行号 until 20
    backtrace/bt 查看当前执行栈的各级函数调用及参数 backtrace
    info/i locals 查看当前栈帧的局部变量值 info locals
    quit 退出 GDB 调试器 quit

    常用命令速查

    命令功能
    gdb 程序名 / quit 或 ctrl+d 进入 / 退出
    l(list) / r(run) 看源码 / 运行到断点或结束
    b 行号 / b 函数名 / info b 设断点 / 看断点
    n / s / finish 逐过程 / 逐语句 / 执行到函数返回
    p 变量 / set var 变量=值 打印 / 现场改值(不改源码)
    c / until 行号 继续到下一断点 / 执行到指定行
    d 编号 / disable 编号 / enable 编号 删 / 禁用 / 启用断点
    display 变量 / undisplay 编号 每次停下自动显示 / 取消
    bt / info locals 看调用栈 / 看当前函数局部变量

    断点编号只增不减(删了 2 再设新的不会补回 2);↑ 重复上一条命令;undisplay 跟的是显示编号不是变量名。只有新增断点时会用到行号,其他情况都是使用断点编号!

    三个调试技巧【重点】

  • watch 变量:值一变就暂停并显示新旧值 —— 查"变量在哪一刻被改了"。
  • set var 变量=值:现场改值验证猜想 —— 不重编译,只改本次运行。
  • 条件断点:
  • 目的语法有 if 吗
    新增条件断点 b 9 if i==30 有 if
    给已有断点加条件 condition 2 i==30 没有 if,2 是断点编号

    cgdb窗口之间转换, ESC进入代码区,上面窗口 i,进入cgdb下面窗口


    8. 附录:换源实操

    步骤主线(两者一致)

    备份 → 下载/替换新源配置 → 清缓存更新 →(可选升级)→ 验证 →(可选恢复)

    CentOS 7 换 yum 源

    sudo mkdir /etc/yum.repos.d/backup # ① 建备份目录
    sudo mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ # ② 备份全部旧源
    sudo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo # ③ 下载阿里云源(清华源二选一)
    sudo yum clean all && sudo yum makecache # ④ 清旧缓存 + 重建【重点:缺这步仍走旧源】
    sudo yum update # ⑤ 更新系统
    sudo yum repolist # ⑥ 验证
    # 恢复:sudo mv /etc/yum.repos.d/backup/*.repo /etc/yum.repos.d/

    Ubuntu 换 apt 源

    sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak # ① 备份(apt 是单个文件)
    sudo wget -O /etc/apt/sources.list http://mirrors.aliyun.com/repo/ubuntu-sources.list # ② 替换(二选一)
    # ③ 检查每行都含 focal(Ubuntu 20.04 代号),混入 bionic/jammy 要删【易错】
    sudo apt update # ④ 更新缓存
    sudo apt policy # ⑤ 验证
    # 恢复:sudo cp /etc/apt/sources.list.bak /etc/apt/sources.list && sudo apt update

    【易混】 yum 源 = /etc/yum.repos.d/ 目录下多个 .repo 文件;apt 源 = /etc/apt/sources.list 单个文件。


    易混淆对比速查卡

    主题AB
    链接方式 动态:运行时加载 .so,gcc 默认 静态:复制进程序 .a,需 -static
    vim 撤销 u 撤销(后退) ctrl+r 恢复(前进)
    回车换行 \\r 回行首 \\n 下一行
    gdb 步进 n 不进函数(F10) s 进函数(F11)
    条件断点 新增 b 9 if i==30(有 if) 追加 condition 2 i==30(无 if)
    自动变量 $@ 目标 / $< 第一个依赖 $^ 全部依赖
    安装源 base 标准稳定 epel 扩展求全
    软件源配置 yum:.repo 目录 apt:sources.list 单文件
    make 时间 Modify 内容修改(make 看这个) Change 属性变化 / Access 访问

    下期预告

    线程

    结语

      本文到此结束,感谢大家的阅读!如果觉得本文对你有所帮助,欢迎点赞、收藏、关注,也欢迎在评论区一起交流讨论。   也欢迎订阅我的 深入理解 C++系列:从语法入门到底层原理,系统掌握现代 C++ 算法系列:从入门到精通,蓝桥杯、ACM、LeetCode 与面试算法全路线 快速复习系列:知识梳理、查漏补缺,考前冲刺必备 Linux系列:从零带你系统学习Linux系统,深入理解计算机组成原理!


      愿每一次敲下键盘,都比昨天更进一步!

      愿每一行代码落下,都让未来多一种可能!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux系列(05)——基础开发工具(下)makefile、git、gdb
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!