当前位置: 首页 > news >正文

008 Linux 开发工具(下) —— make、Makefile、git和gdb

🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022

在这里插入图片描述

文章目录

  • Linux 开发工具(下)
    • Linux 项目自动化构建工具 —— `make` / `Makefile`
      • 1. 什么是 `make` 和 `Makefile`?
        • 为什么需要 `make`?
      • 2. `make` 的基本工作原理
        • 基本逻辑
        • 规则格式
      • 3. `make` 的工作流程
      • 4. 伪目标 `.PHONY` 与放置位置的讨论
        • 关于 `.PHONY: clean` 的放置位置
      • 5. 常见符号与自动变量的使用
        • `@` —— 命令隐藏符
        • 自动变量 `$^` 与 `$@`
      • 6. 进阶技巧(了解)
        • 变量定义与使用
        • 模式规则
      • 传道解惑
        • ==文件 = 文件内容 + 文件属性==
    • Linux 下第一个小程序——进度条
      • 1. 换行 vs 回车:键盘上的时光机
      • 2. 行缓冲区:快递员的打包习惯
        • 实验验证与现象分析
        • Shell 提示符的输出行为
        • 为什么覆盖?
      • 3. 倒计时实现
      • 4. 由于进度条相关文章内容过长,详见下一篇文章!
    • `git ` 的使用
      • 一、Git 的本质:时间管理大师的 "时光机"
      • 二、Linus Torvalds:被逼出来的创新
      • 三、Git 的革命性突破
      • 四、Git 的现代发展
      • 五、Git 改变了软件开发的方式
      • 六、`git` 的使用([Git 教程](https://liaoxuefeng.com/books/git/introduction/index.html))
        • Git 的核心概念
        • Git 的工作流程
        • 1. 安装 Git
        • 2. 配置 Git
        • 3. 创建/初始化仓库
        • 4. 克隆远程仓库
        • 5. 查看仓库状态
        • 6. 添加文件到暂存区
        • 7. 提交更改
        • 8. 查看提交历史
        • 9. 查看远程仓库
        • 10. 添加/连接远程仓库
        • 11. 推送本地分支到远程仓库
        • 12. 拉取远程仓库的更新
        • 13. 撤销工作区的修改
        • 14. 撤销暂存区的修改
        • 15. 撤销提交
        • 16. 创建标签
        • 17. 查看标签
        • 18. 推送标签到远程仓库
        • 19. 查看差异
        • 20. 查看远程分支
        • 21. 删除远程分支
      • 1. Git 只会记录已添加到暂存区(staging area)的修改
      • 2. Git 忽略某些文件:`.gitignore`
      • 3. Git 配置文件的管理
    • Linux 调试器 —— `gdb` 的使用
      • 一、背景知识
      • 二、Windows IDE 对应功能
      • 三、`gdb` 常用命令
        • 1. 查看源代码
        • 2. 运行程序
        • 3. 单步执行
        • 4. 设置断点
        • 5. 删除断点
        • 6. 继续执行
        • 7. 查看和修改变量
        • 8. 跟踪变量
        • 9. 查看函数调用栈
        • 10. 查看局部变量
        • 11. 跳转到指定行
        • 12. 退出函数
    • 共勉

Linux 开发工具(下)

Linux 项目自动化构建工具 —— make / Makefile

1. 什么是 makeMakefile

在大型软件项目中,源代码通常散布在多个文件和目录中。为了高效地管理这些文件之间的依赖关系并实现自动化编译,Linux 提供了非常强大的工具 —— makemake 是一个自动化构建工具,它通过读取 Makefile 文件中的规则来决定如何编译和链接程序。Makefile 是一个文本文件,其中定义了项目的依赖关系和构建规则。

为什么需要 make
  • 自动化构建:手动编译多个源文件并管理它们之间的依赖关系非常繁琐,make 可以自动化这一过程。
  • 增量编译make 只会重新编译那些被修改的文件及其依赖项,从而节省编译时间。
  • 跨平台兼容Makefile 可以在不同的平台上使用,只需稍作修改。

2. make 的基本工作原理

make 的核心功能是根据文件的修改时间自动判断哪些文件需要重新编译。具体来说,它会根据文件间的依赖关系,确保只有修改过的部分被重新编译,避免无谓的重复编译。

基本逻辑
  • 目标文件与依赖关系
    make 通过比较目标文件与依赖文件的修改时间来决定是否需要重新编译。如果目标文件不存在,或者依赖文件较新,make 就会重新执行对应的编译命令。

  • 举例说明:假设我们有如下的依赖关系:

    • hello 依赖于 hello.o
    • hello.o 依赖于 hello.s
    • hello.s 依赖于 hello.i
    • hello.i 依赖于 hello.c

    hello.c 被修改后,make 会检查文件的修改时间,并按照依赖关系从 hello.c 开始,逐层更新直到最终目标 hello

规则格式
目标文件:依赖文件命令

例如:

hello: hello.ogcc hello.o -o hello

如果 hello.o 发生了变化,make 会执行 gcc hello.o -o hello 来生成目标文件 hello


3. make 的工作流程

Makefile 中的依赖关系往往构成一个“栈式”结构,即从最终目标开始,逐层向下寻找依赖,直到最初的源文件。

  1. 查找文件make 会在当前目录下查找名为 Makefilemakefile 的文件。
  2. 确定目标文件:找到文件后,它会查找文件中的第一个目标文件(如示例中的 hello)并将其作为最终的目标文件。
  3. 检查依赖关系:如果目标文件不存在,或者其依赖文件的修改时间比目标文件更新,则执行相应的命令生成目标文件。
  4. 递归处理依赖:如果目标文件的依赖文件(如 hello.o)不存在,则会进一步在当前文件中查找该依赖文件的规则,并依此进行生成。
  5. 完成编译:按照依赖关系一层一层地处理,直到最终生成第一个目标文件。
  6. 错误处理:如果在依赖关系的查找过程中出现错误(如最后被依赖的文件找不到),make 会直接退出并报错;对于命令执行的错误或编译不成功的情况,make 不会进行处理。

例如下面这个 Makefile 片段展示的完整依赖链:

# 目标文件 hello 依赖于 hello.o
hello: hello.o# gcc 用于编译链接生成可执行文件 (Executable)gcc hello.o -o hello# 目标文件 hello.o 依赖于 hello.s
hello.o: hello.s# -c 表示只编译生成目标文件,不进行链接gcc -c hello.s -o hello.o# 目标文件 hello.s 依赖于 hello.i
hello.s: hello.i# -S 表示生成汇编代码 (Assembly code)gcc -S hello.i -o hello.s# 目标文件 hello.i 依赖于 hello.c
hello.i: hello.c# -E 表示只预处理生成中间文件gcc -E hello.c -o hello.i
  • 依赖判断:当修改了 hello.c 后,hello.i 的时间戳就会落后于 hello.c,从而触发后续所有目标的重新编译。
  • 自动化依赖推导make 会根据目标与依赖之间的时间比较,自动“回溯”整个依赖链,直到确定哪些文件需要重新生成。

4. 伪目标 .PHONY 与放置位置的讨论

定义:伪目标(如 clean)一般用于清理工程中的目标文件,它们没有被第一个目标文件直接或间接关联。可以通过命令(如 make clean)显式执行其后的命令。伪目标的特性是 总是被执行,不会因为文件的存在而被忽略。

例如在 Makefile 中,我们经常会定义一些不对应实际文件的辅助目标,clean 目标通常用于删除编译产生的中间文件。为避免与同名文件产生冲突,我们使用 .PHONY 声明该目标总是需要执行:

.PHONY: clean
clean:@rm -f hello.i hello.s hello.o hello
# 使用 `.PHONY` 声明后,`make clean` 会始终执行清理命令,即使当前目录下存在名为 `clean` 的文件。
关于 .PHONY: clean 的放置位置

放在开头或结尾: 无论将 .PHONY: clean 放在 Makefile 的开头还是结尾,其功能是相同的,都会告诉 make “clean” 不是一个真实存在的文件。然而,从 代码可读性维护性 的角度来看,通常建议将辅助目标(如 clean)放在文件的末尾。这样做可以:

  • 保持主构建规则的集中:主要的目标和依赖关系放在上面,便于开发者快速了解构建流程。
  • 逻辑分明:清理等辅助目标作为附加功能放在末尾,形成明显的区分。

5. 常见符号与自动变量的使用

在编写 Makefile 时,合理使用一些特殊符号和自动变量能使文件更加简洁与灵活。

@ —— 命令隐藏符

在规则的命令前加上 @ 符号,可以在执行时不将该命令打印到终端。例如:

clean:@rm -f hello.i hello.s hello.o hello
  • 作用:使得执行 make clean 时不会在终端中显示 rm -f ... 这一行命令,从而使命令输出更干净,只显示必要的信息。
自动变量 $^$@
  • $@:代表规则中的目标文件(Target)。
  • $^:代表规则中所有的依赖文件(Prerequisites),通常用来减少重复输入依赖文件列表。

示例: 假设有多个依赖文件构成目标文件,我们可以这样写:

hello: hello.o util.o# $^ 表示所有依赖,即 "hello.o util.o"(:右边部分,若有多个,用空格隔开)# $@ 表示目标,即 "hello"(:左边部分)gcc $^ -o $@

好处:使用自动变量能让规则更灵活且易于维护,尤其当依赖项较多时,避免重复书写目标和依赖文件名称。

6. 进阶技巧(了解)

变量定义与使用

Makefile 中,我们可以定义变量来简化重复的代码。例如:

CC = gcc
CFLAGS = -Wall -O2myprogram: main.o utils.o$(CC) $^ -o $@main.o: main.c utils.h$(CC) $(CFLAGS) -c main.c -o main.outils.o: utils.c utils.h$(CC) $(CFLAGS) -c utils.c -o utils.o
  • CC:定义编译器为 gcc
  • CFLAGS:定义编译选项为 -Wall -O2
模式规则

当项目中有多个相似的文件需要编译时,可以使用模式规则来简化 Makefile。例如:

%.o: %.c$(CC) $(CFLAGS) -c $< -o $@
  • %.o:表示所有以 .o 结尾的目标文件。
  • %.c:表示所有以 .c 结尾的源文件。
  • $<:表示第一个依赖文件。

传道解惑

文件 = 文件内容 + 文件属性

在计算机系统中,文件不仅仅是包含数据内容的容器,它还具有一些属性,描述了文件的元数据(metadata)。这些属性包括文件的权限、修改时间、访问时间、所有者、大小等。

我们可以将文件的概念表示为:文件 = 文件内容 + 文件属性

1. 文件内容(File Content)

文件内容是文件实际存储的数据。对于文本文件,它通常是可读的字符串;对于二进制文件,它可以是任意的数据,如图像、音频、视频或程序代码等。文件内容是文件的核心部分,用户创建、编辑和删除文件时,主要涉及文件内容的操作。

例如,一个文本文件 hello.txt 的内容可能如下:

Hello, World!

2. 文件属性(File Attributes)

文件属性是描述文件元信息的数据,通常是系统自动管理的。这些属性并不直接涉及文件的内容,但它们在文件的管理、访问和权限控制中起着至关重要的作用。常见的文件属性包括:

  • 文件权限(Permissions):指定哪些用户或用户组可以读取、写入或执行文件。
  • 所有者(Owner):文件的创建者或拥有者。
  • 创建时间(Creation Time):文件首次创建的时间。
  • 修改时间(Modification Time, mtime):文件内容最后一次修改的时间。
  • 访问时间(Access Time, atime):文件最后一次被访问的时间(无论是读取、执行还是其他)。
  • 改变时间(Change Time, ctime):文件的元数据(如权限、所有者、位置)最后一次修改的时间。

3. 文件属性详解

  • 修改时间(mtime:修改时间表示文件内容上次修改的时间。每次文件内容改变时,mtime 就会更新。例如,如果你编辑并保存一个文本文件,那么该文件的 mtime 就会更新为当前时间。

  • 访问时间(atime:访问时间表示文件上次被访问的时间。访问可以是读取文件内容、执行程序文件等操作。每当文件被读取时,atime 就会更新。需要注意的是,有些操作系统会进行优化,使得访问文件时不更新 atime,从而减少磁盘 I/O。例如,使用 cat 命令读取文件内容会更新 atime

  • 改变时间(ctime:改变时间表示文件的元数据(如文件权限、文件名、所有者等)最后一次改变的时间。ctime 仅在文件的属性发生变化时更新,而不是文件内容。例如,如果改变了文件的权限或文件的所有者,ctime 会发生变化。

4. 文件操作与属性变化

  • 修改文件内容: 当我们修改文件内容时,文件的 mtime 会更新,而 atimectime 通常不变,除非文件本身被移动、重命名或修改其他元数据。例如,使用 echo "new content" > file.txt 命令修改 file.txt 文件内容时,文件内容发生了变化,因此 mtime 会更新。

  • 访问文件: 当我们访问文件(例如读取文件内容)时,文件的 atime 会更新,但 mtimectime 不会发生变化。例如,使用 cat file.txt 读取文件内容时,文件的 atime 会更新,表示文件被访问了。

  • 修改文件属性: 当我们修改文件的权限、所有者等属性时,文件的 ctime 会更新。这个时间是文件元数据的变化标志,而不是文件内容本身的变化。例如,使用 chmod 修改文件权限时,ctime 会更新,但 mtimeatime 不会发生变化。

5. 文件属性的查看与修改

在 Linux 系统中,可以使用 ls 命令查看文件的基本属性(如权限、所有者、时间等):

ls -l file.txt

输出例子:

-rw-r--r-- 1 user user 128 Feb 7 10:15 file.txt

其中:

  • -rw-r--r-- 是文件的权限。
  • 1 是硬链接数量。
  • user 是文件的所有者。
  • user 是文件的所属用户组。
  • 128 是文件的大小。
  • Feb 7 10:15 是文件的 mtime(修改时间)。

要查看文件的 atimectime,可以使用 stat 命令:

stat file.txt

输出例子:

File: file.txt
Size: 128       Blocks: 8          IO Block: 4096   regular file
Device: 803h/2051d   Inode: 12345678  Links: 1
Access: 2025-01-01 10:00:00.000000000
Modify: 2025-01-01 10:00:00.000000000
Change: 2025-01-01 10:00:00.000000000
  • Accessatime(访问时间)
  • Modifymtime(修改时间)
  • Changectime(更改时间)

Linux 下第一个小程序——进度条

1. 换行 vs 回车:键盘上的时光机

  • 换行(Line Feed, \n:光标移动到 下一行,但水平位置不变 。在一些系统中,换行符是 \n,也有的系统中(如 Windows)回车和换行会一起使用。
  • 回车(Carriage Return, \r:光标回到 当前行的行首,不换行 。在很多操作系统中,回车是用来将光标重置到行首的位置。换行符通常是 \r

生活场景比喻:想象你用打字机写文章,打完一行字后需要做两个动作:

  • 换行(Line Feed):把纸向上推一行(对应 \n
  • 回车(Carriage Return):把打印头移回最左侧(对应 \r

2. 行缓冲区:快递员的打包习惯

行缓冲区(Line Buffering)是指输出流的数据并不是直接输出到屏幕或终端,而是先存储在缓冲区中。当缓冲区满时,数据才会被输出。这种方式提升了效率,减少了对系统资源的频繁访问。在 C 语言中,printf() 是行缓冲的典型例子。它的工作原理是:当你调用 printf() 打印一行文本时,数据并不是立刻显示,而是先被放到缓冲区中,直到遇到换行符 \n 时,系统才会将缓冲区的内容真正输出到屏幕上。

生活场景比喻:快递员不会每收到一个小物件就立刻送货,而是攒满一车再出发。 行缓冲区 就像这个 “攒满一车” 的规则:

  • 遇到 \n 换行符时立刻 “送货”(刷新缓冲区)
  • 缓冲区满时自动刷新
  • 程序正常结束时也会自动刷新

标准输出(stdout)在终端中是 行缓冲 的,如果没有换行符 \n,缓冲区不会自动刷新,导致内容被覆盖或丢失。

实验验证与现象分析
  • 代码 1

    printf("123456\rAB");
    
    • 现象:只显示 AB。实测 windowvs 2022 显示 AB 456 应该是属于 vs 的“个人行为”。
    • 原因\r 将光标移回行首,但缓冲区未刷新,内容被覆盖或未输出。
  • 代码 2

    printf("123456\rAB\n");
    
    • 现象:终端显示 AB3456
    • 原因\n 触发了缓冲区的刷新,AB 覆盖了 12,然后换行。

  • 代码 3

    printf("123456\rAB");
    fflush(stdout); // 手动刷新缓冲区
    
    • 现象:终端显示 AB3456。细节流程:
      • 123456 被输出到终端,光标停留在 6 后面。
      • \r 将光标移动到行首(即 1 的位置)。
      • AB 被输出,覆盖了前两个字符 12,此时终端内容为 AB3456,光标停留在 3 的位置。
    • 原因fflush(stdout) 强制刷新缓冲区,AB 覆盖了 12

    为什么我看到的只有 AB

    我们不妨修改代码,运行后,3 秒内会看到 AB3456,随后提示符会进行覆盖,这也就是为什么我们看到的只有 AB 了:

    int main()
    {printf("123456\rAB");fflush(stdout);sleep(3); // 暂停3秒,观察输出return 0;
    }
    
Shell 提示符的输出行为
  • 当程序运行结束后,Shell 会立即在 当前光标位置 输出提示符(如 [damai@VM-16-11-centos coding]$)。
  • 由于程序结束时,光标停留在 3 的位置,Shell 提示符会从 3 的位置开始输出,覆盖 掉后面的内容 3456
为什么覆盖?
  • 终端的工作机制:终端是一个字符设备,它会严格按照光标位置输出内容。如果光标不在行尾,新输出的内容会从光标位置开始覆盖已有的内容。
  • Shell 提示符的输出:Shell 提示符不会主动换行,而是从当前光标位置开始输出。因此,如果程序没有将光标移动到行尾或换行,提示符就会覆盖程序输出的内容。

知道了这些内容,我们就可以尝试看看下面代码运行的结果了:

#include <stdio.h>
#include <unistd.h>int main()
{// 实验1:无换行符,无刷新printf("123456\rAB");sleep(2); 			// 等待2秒观察现象printf("\n"); 		// 换行以刷新缓冲区// 实验2:有换行符printf("123456\rAB\n");sleep(2); 			// 等待2秒观察现象// 实验3:手动刷新printf("123456\rAB");fflush(stdout); 	// 手动刷新sleep(2); 			// 等待2秒观察现象printf("\n"); 		// 换行return 0;
}

在来一遍,深刻理解

  1. 现象 1:没有换行符 \n 的情况下调用 printf()

    #include <stdio.h>int main()
    {printf("hello Makefile!");sleep(3);return 0;
    }
    

    现象:这时 “hello Makefile!” 会先存入缓冲区,不会立刻显示在屏幕上。直到程序结束,缓冲区中的内容才会被显示出来。所以程序在运行完 sleep(3) 后,才会看到输出结果。

  2. 现象 2:加上换行符 \n

    #include <stdio.h>int main()
    {printf("hello Makefile!\n");sleep(3);return 0;
    }
    

    现象:这时,换行符 \n 会强制刷新缓冲区,所以 “hello Makefile!” 会立即输出,等待 3 秒后程序结束。

  3. 现象 3:调用 fflush(stdout) 强制刷新缓冲区:

    #include <stdio.h>
    #include <unistd.h>
    int main()
    {printf("hello Makefile!");fflush(stdout);  // 强制刷新缓冲区sleep(3);return 0;
    }
    

    现象:调用 fflush(stdout) 后,printf() 输出的内容会立即显示,无论是否遇到换行符,缓冲区中的内容都会被强制刷新到屏幕上,等待 3 秒后程序结束。


3. 倒计时实现

理解了回车换行和行缓冲区的概念后,我们再来做一个倒计时的实现,相信下面的代码很容易理解:

int main()
{int cnt = 10;while (cnt >= 0){printf("%d\r", cnt);fflush(stdout);cnt--;sleep(1);}return 0;
}

运行一下,会发现倒计时的显示效果是:10908070……,而不是预期的 1098 → ……。数字宽度不一致, 当 cnt 为两位数 10 时,输出 10;而当 cnt 变为一位数(如 9)时,输出的是 9。因此,当从两位数输出变为一位数输出时,上一轮输出中的多余字符(例如 10 中的 1)依然残留在屏幕上。我们做以下修改即可:

printf("%2d\r", cnt);	// 调整输出格式:如果 cnt 只有一位数,则会在数字前补空格

再次运行会发现,倒计时站两个字符,前一个字符空出,只有后一个字符在变化,显得不太正常,我们接着做修改:

printf("%-2d\r", cnt);	// 修改对其规则:左对齐

于是,我们的倒计时就实现了:

#include <stdio.h>
#include <unistd.h> // 包含 sleep 函数int main()
{int cnt = 10; // 初始化倒计时值while (cnt >= 0) // 循环直到倒计时结束{printf("%-2d\r", cnt); // 格式化输出,左对齐,固定宽度为2,并覆盖之前输出中多余的字符fflush(stdout);        // 强制刷新缓冲区,确保立即输出cnt--;                 // 倒计时减1sleep(1);              // 暂停1秒}printf("倒计时结束!\n"); // 倒计时完成后输出提示信息return 0;
}

4. 由于进度条相关文章内容过长,详见下一篇文章!


git 的使用

这里会简单叙述 Git 的诞生与核心价值,然而关于 git 远远不止于此,需要大量内容才能将 git 讲清楚,所以,未来我会专门出一个专题来对 git 进行详细的讲解!

一、Git 的本质:时间管理大师的 “时光机”

要理解 Git,我们可以想象一个科幻场景:假设你正在写一本小说,每天创作的新章节都会生成一个独立的时间胶囊。某天你发现主角设定出了问题,只需打开对应日期的胶囊就能恢复原貌。Git 本质上就是这样一个分布式时光机。它通过记录文件的 “快照” 而非差异(如 SVN),让每个开发者电脑都保存完整的版本库。例如:

  • 张三改实验报告:李四作为 “版本管家”,每次收到修改都存档并标注(git commit
  • 多人协作写代码:就像乐队分声部排练,Git 确保所有乐谱修改能精准合并(git merge

二、Linus Torvalds:被逼出来的创新

Linux 之父林纳斯·托瓦兹(Linus Torvalds)与 Git 的渊源,堪称技术界的 “复仇爽文”:

在 2002 年以前,lucubrate Linux 的庞大社区还处于一种极度原始的代码管理状态,依靠着手工合并代码来维系项目 Progress。这无疑给参与其中的开发者们带来了巨大的不便与低效,令人不禁联想起原始部落艰难求生的画面。

随后,商业工具 BitKeeper 的引入仿佛曙光初现,为 Linux 内核的代码管理带来了短暂的清朗时期。却不曾想,因社区成员尝试逆向工程这一敏感行为,(BitKeeper 开发团队)愤然剥夺了 Linux 社区的使用授权。正如同好不容易借到的宝藏工具突然被夺走,开发者们再度陷入了深深的困境,这无疑是压垮骆驼的最后一根稻草。

在这一刻,Linus Torvalds,Linux 之父,心中愤怒与不甘的火焰熊熊燃烧。他深知,若不能寻得有效的解决之道,Linux 项目的未来将满是荆棘。于是,两周,仅仅两周的时间,他凭借着过人的智慧与毅力,完成了 Git 原型的编写。而一个月内,Linux 内核便成功迁移到这个自主开发的全新系统上,整个过程仿若一场奇迹。

" 就像被房东赶走的窘迫之人,在绝望之中亲手盖出了一栋更豪华的别墅。" —— 网友评价

三、Git 的革命性突破

对比传统版本控制工具,Git 实现了三大飞跃:

特性集中式(如 SVN)Git 分布式
网络依赖必须联网提交本地即可完成所有操作
数据安全中央服务器故障即丢失历史每个副本都是完整备份
分支管理创建/合并分支耗时轻量级分支秒级切换

四、Git 的现代发展

Git 自诞生以来,逐渐在全球范围内普及并成为开发人员的标准工具之一。以下是 Git 现代发展的几个关键点:

1. GitHub 的崛起

2008 年,GitHub 上线,它为 Git 提供了一个强大的托管平台,使得开发者可以方便地在线托管代码,并且与其他开发者协作。GitHub 通过引入 Pull Request(PR)功能,让 Git 的协作模式更加高效,推动了开源社区的快速发展。很多知名的开源项目(如 jQuery、Node.js 等)都迁移到了 GitHub。

2. 中国化适配:Gitee 等本土平台

由于 GitHub 在中国大陆访问受到一定限制,本土平台如 Gitee 等逐渐兴起。Gitee 不仅解决了访问速度的问题,还提供了针对中国开发者的多种本地化功能,使得 Git 在国内的普及更加顺利。

3. 可视化工具的普及

随着 Git 的普及,越来越多的可视化工具如 GitKraken、SourceTree 等应运而生。这些工具通过图形界面简化了 Git 的操作,使得即使是没有命令行经验的开发者也能轻松上手。Git 的复杂操作变得更加直观和易用,降低了学习成本。

4. 版本控制的精确性和回溯功能

Git 的最大优势之一就是其强大的回溯能力。当开发者说“我回滚一下”,实际上就是“导演喊‘Cut!’重拍第 3 幕”。每个 commit 就像电影中的一个镜头,都可以精确回溯、查看和恢复,确保每一步的修改都能被追溯和复原。

五、Git 改变了软件开发的方式

从 Linus Torvalds 的灵感到 Git 成为全球开发者的标准工具,Git 无疑已经成为软件开发的核心技术之一。Git 的核心价值不仅仅在于它如何管理版本,更在于它如何改变了开发者的工作方式、提高了协作效率,并且推动了开源软件的发展。

正如 Linus 所说:“Talk is cheap. Show me the code.” Git 用代码改变了世界,它的出现不仅仅是一个工具的革命,更是一种工作方式、协作方式和思想方式的革命。Git 的设计思想和实践,使得开发者能够更加专注于代码的创作,而不必过度担心版本管理的问题。

Git 不仅是一个技术工具,更是一种创新精神的体现——快速响应变化、追求效率、关注团队协作、并持续优化。它的成功,也象征着开源社区强大的生命力和创新能力。

六、git 的使用(Git 教程)

Git 的核心概念
  1. 仓库(Repository):Git 仓库是项目的核心,包含了项目的所有文件和历史记录。每个开发者都有一个完整的仓库副本。
  2. 提交(Commit):每次提交都是项目的一个快照,记录了文件的更改和提交信息。
  3. 分支(Branch):分支是开发中的独立线路,允许开发者在不同的分支上并行工作。
  4. 合并(Merge):将不同分支的更改合并到一起,确保代码的一致性。
  5. 克隆(Clone):从远程仓库复制一个完整的仓库到本地。
  6. 拉取(Pull):从远程仓库获取最新的更改并合并到本地分支。
  7. 推送(Push):将本地的更改上传到远程仓库。
Git 的工作流程
  1. 初始化仓库:使用 git init 命令创建一个新的 Git 仓库。
  2. 添加文件:使用 git add 命令将文件添加到暂存区。
  3. 提交更改:使用 git commit 命令将暂存区的更改提交到仓库。
  4. 创建分支:使用 git branch 命令创建一个新的分支。
  5. 切换分支:使用 git checkout 命令切换到不同的分支。
  6. 合并分支:使用 git merge 命令将不同分支的更改合并到一起。
  7. 查看历史:使用 git log 命令查看提交历史。

我们在 Linux CentOS 7 云服务器上使用 Xshell 连接并操作 Git 时的一些常用的 Git 命令:

1. 安装 Git

首先,确保你的 CentOS 7 系统上已经安装了 Git。如果没有安装,可以使用以下命令进行安装:

# 更新 yum 软件包
sudo yum update -y# 安装 Git
sudo yum install -y git

安装完成后,可以通过以下命令检查 Git 版本,确认安装成功:

git --version
2. 配置 Git

第一次使用 Git 之前,需要配置用户名和邮箱,这些信息会出现在每次提交的记录中。

git config --global user.name "your_name"   # 设置用户名
git config --global user.email "your_email@example.com"  # 设置邮箱

通过以下命令查看当前的 Git 配置:

git config --list
3. 创建/初始化仓库

在当前目录下创建一个新的 Git 仓库,使用以下命令(这会在当前目录下生成一个 .git 目录,用于存储 Git 的版本控制信息):

git init
4. 克隆远程仓库

如果想从远程仓库克隆一个项目到本地,可以使用 git clone 命令,这会将远程仓库的内容克隆到当前目录下的一个新文件夹中:

git clone https://github.com/username/repository.git
5. 查看仓库状态

使用以下命令可以查看当前仓库的状态,包括哪些文件被修改、哪些文件被暂存等:

git status
6. 添加文件到暂存区

在对文件进行修改后,需要将文件添加到暂存区(Stage),以便后续提交:

git add file_name	# 例如:git add temp.txt ,添加文件到暂存区

如果想添加所有修改过的文件,可以使用:

git add .
7. 提交更改

将暂存区的文件提交到本地仓库,-m 选项后面跟的是本次提交的描述信息。描述信息就是提交日志,尽量提交有意义的信息!:

git commit -m "Your commit message"		# 例如:git commit -m "第一次提交" ,提交到本地仓库
8. 查看提交历史

使用以下命令可以查看当前仓库的提交历史:

git log

还可以通过 --oneline 选项简化输出:

git log --oneline
9. 查看远程仓库

查看当前配置的远程仓库:

git remote -v
10. 添加/连接远程仓库

添加/连接一个新的远程仓库:

git remote add origin 远程版本库的URL
# 例如:git remote add origin https://github.com/your_username/your_repo.git
11. 推送本地分支到远程仓库

将本地分支的提交推送到远程仓库:

git push origin 分支名称
git push -u origin master		# 将 master 分支推送,并设置默认上游分支(常用)
12. 拉取远程仓库的更新

从远程仓库拉取最新的更改并合并到当前分支:

git pull origin 分支名称
git pull origin master		# 拉取远程仓库的最新代码并合并
13. 撤销工作区的修改

撤销工作区中某个文件的修改,恢复到最近一次提交的状态:

git checkout -- 文件名称
# 例如:git checkout -- file.txt	丢弃工作区修改,恢复到最后一次提交状态
14. 撤销暂存区的修改

将某个文件从暂存区撤回到工作区:

git reset HEAD 文件名称
15. 撤销提交

撤销上一次提交,只回退 commit,保留代码修改:

git reset --soft HEAD~1

撤销最近一次提交,并将更改放回工作区:

git reset --soft HEAD^

如果你想撤销提交并丢弃更改,可以使用:

git reset --hard HEAD^
16. 创建标签

创建一个新的标签:

git tag 标签名称
17. 查看标签

查看所有标签:

git tag
18. 推送标签到远程仓库

将本地标签推送到远程仓库:

git push origin 标签名称
19. 查看差异

查看工作区与暂存区的差异:

git diff

查看暂存区与最新提交的差异:

git diff --cached
20. 查看远程分支

查看远程仓库的所有分支:

git branch -r
21. 删除远程分支

删除远程仓库的指定分支:

git push origin --delete 分支名称

至于分支管理、多人协作和冲突管理等操作暂时用不到,放到以后再说,有兴趣可自行百度。


除了前面提到的基本操作,Git 在使用时还存在一些常见的注意事项(这里提到部分内容,如有其他问题需勤于百度):

1. Git 只会记录已添加到暂存区(staging area)的修改

Git 不会自动跟踪所有修改。它只会跟踪你手动添加到暂存区的修改,即 默认记录修改部分(工作区和暂存区)。也就是通过 git add 添加的文件或更改。

  • 工作区(Working Directory):对文件的任何修改都首先体现在工作区。
  • 暂存区(Staging Area):通过 git add 命令将修改暂存到 Git 中的暂存区。
  • 本地仓库(Repository):通过 git commit 命令,将暂存区的修改提交到本地仓库。

关键点(工作流程): 只有 git add 命令添加到暂存区的文件才会被 git commit 提交(修改文件 → git add 添加到暂存区 → git commit 提交到仓库)。

示例:

echo "This is a test" > file.txt      # 修改文件
git status                            # 查看状态,file.txt 会显示为修改状态
git add file.txt                      # 添加到暂存区
git commit -m "Updated file.txt"      # 提交修改

忘记 git add 的后果

如果修改了文件但没有执行 git add,然后直接 git commit,Git 是不会将这些修改提交的。因此,Git 只会记录已经添加到暂存区的修改。

2. Git 忽略某些文件:.gitignore

如果不希望某些文件被 Git 跟踪(如编译出来的二进制文件、IDE 配置文件等),可以使用 .gitignore 文件来指定不需要跟踪的文件或文件夹。

创建 .gitignore

# 例如,忽略所有 *.log 文件
echo "*.log" >> .gitignore

然后 git add .gitignore 提交 .gitignore 文件!!!

关键点: .gitignore 文件是 Git 跟踪文件的方式之一。

3. Git 配置文件的管理

Git 配置文件分为三种层级:

  • 系统级:影响系统上所有用户的配置,通常位于 /etc/gitconfig
  • 全局级:影响当前用户的配置,通常位于 ~/.gitconfig
  • 仓库级:只对当前仓库生效,位于仓库的 .git/config

可以通过以下命令查看和修改 Git 配置:

git config --global user.name "Your Name"     # 设置全局用户名
git config --global user.email "you@example.com"  # 设置全局邮箱

查看当前配置:

git config --list

Linux 调试器 —— gdb 的使用

一、背景知识

在程序开发过程中,为了有效定位和修复代码中的错误,调试工具起着至关重要的作用。程序的发布方式主要有两种:debug 模式和 release 模式。

  • debug 模式:此模式下编译生成的程序包含了丰富的调试信息,例如变量名、函数名、源代码行号等,方便开发者借助调试工具进行错误排查。不过,debug 模式通常会增加程序的体积,并且在一定程度上降低运行效率。
  • release 模式:旨在提供高性能的可执行程序,编译时会进行各种优化,剔除不必要的调试信息,以确保程序运行速度和资源利用率最大化。在 Linux 系统中,使用 gcc 或 g++ 编译器编译出来的二进制程序,默认是 release 模式。

若要使用 gdb(GNU Debugger)对程序进行调试,必须在源代码生成二进制程序时,加上 -g 选项。这样,编译器会在生成的可执行文件中嵌入调试所需的信息,使得 gdb 能够准确地关联程序运行状态与源代码。

编译生成调试信息

假设有一个源文件 temp.c-g 告诉编译器生成调试信息,-o temp 指定输出文件名。可以通过以下命令编译程序并生成调试信息:

gcc -g temp.c -o temp

启动 gdb

gdb 启动时需要提供一个已经编译好的二进制文件。假设我们有一个名为 temp 的程序文件,通过以下命令启动 gdb 准备进行调试:

gdb temp

退出 GDB

退出 GDB 的方式有两种:

  • 输入 quitq 命令(常用)。
  • 按下 Ctrl + D

二、Windows IDE 对应功能

对于 Windows 用户习惯的 IDE 调试工具(如 Visual Studio),gdb 也提供了类似的功能。例如:

  • 设置断点:在源代码中设置断点,并在程序执行时停止。
  • 单步调试:支持单步执行,可以选择逐行执行代码,或者进入函数内调试。
  • 变量观察:支持查看和修改变量的值,并可以在调试过程中动态修改它们。
  • 堆栈跟踪:查看当前调用栈以及每个函数的参数。

与 Windows 中的 IDE 相比,gdb 是命令行工具,因此需要通过命令行输入调试命令,但功能是非常强大的。


三、gdb 常用命令

GDB 提示符的作用

  • 当你启动 GDB 并加载程序后,GDB 会进入交互模式,显示 (gdb) 提示符。
  • 这个提示符表示 GDB 正在等待你输入命令。
  • 你只需要在 (gdb) 后面输入命令,按回车执行。

例如:

(gdb) break main	# (gdb) 是 GDB 的提示符,只需要输入 break main 并按回车即可
1. 查看源代码

listl:查看源代码。

  • list <行号>:从指定行号开始显示源代码。
  • list <函数名>:显示指定函数的源代码。
  • 默认每次显示 10 行,按回车键继续显示后续内容。
(gdb) list 10      # 显示从第 10 行开始的源代码
(gdb) list         # 显示当前行及接下来的 10 行
(gdb) l main       # 显示 main 函数的源代码
2. 运行程序

runr:从头开始运行程序。

  • 如果程序需要参数,可以在 run 后面加上参数:
(gdb) run           	# 启动程序的执行,不带任何命令行参数
(gdb) run arg1 arg2   	# 启动程序的执行,并传递两个命令行参数:arg1 和 arg2
3. 单步执行
  • nextn:单步执行(不进入函数内部)。
  • steps:单步执行(进入函数内部)。
(gdb) next		# 单步执行
(gdb) step		# 进入函数内部
4. 设置断点

breakb:设置断点。

  • break <行号>:在指定行设置断点。
  • break <函数名>:在函数入口处设置断点。
  • info breakpoints:查看当前设置的所有断点及其状态。
(gdb) break 20				# 在第 20 行设置断点
(gdb) break main			# 在 main 函数的开头设置断点
(gdb) info breakpoints    	# 查看所有断点信息
5. 删除断点
  • delete breakpoints:删除所有断点。
  • delete breakpoints n:删除序号为 n 的断点。
  • disable breakpoints:禁用所有断点,使其在下次调试时不起作用。
  • enable breakpoints:启用所有已禁用的断点。
(gdb) delete breakpoints     # 删除所有断点
(gdb) delete breakpoints 1   # 删除序号为 1 的断点
(gdb) disable breakpoints    # 禁用所有断点
(gdb) enable breakpoints     # 启用所有断点
6. 继续执行

continuec:从当前位置继续执行,直到遇到下一个断点或程序结束。

(gdb) continue    # 继续执行程序
7. 查看和修改变量

printp:打印变量的值。

  • print <变量名>:打印变量的值。
  • print <表达式>:计算并打印表达式的值。
(gdb) print x			# 打印变量 x 的值
(gdb) print x + y

set var 变量名=值:修改变量的值。

(gdb) set var x=10    # 将变量 x 的值修改为 10
8. 跟踪变量
  • display <变量名>:每次程序暂停时,自动打印变量的值。
  • undisplay <编号>:取消对变量的跟踪。
(gdb) display x     # 每次停止时显示变量 x 的值
(gdb) undisplay     # 取消所有跟踪变量
9. 查看函数调用栈

backtracebt:查看当前函数的调用栈(包括参数和调用位置)。

(gdb) backtrace      # 查看函数调用栈
10. 查看局部变量

info locals:查看当前函数的局部变量。

(gdb) info locals	# 查看当前栈帧的局部变量
11. 跳转到指定行

until <行号>:跳转到指定行。

(gdb) until 30		# 跳转到第30行
12. 退出函数

finish:执行完当前函数并暂停。

(gdb) finish	# 执行完当前函数并暂停

共勉

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

http://www.lqws.cn/news/498403.html

相关文章:

  • VitePress搭建静态博客
  • logstash读取kafka日志写到oss归档存储180天
  • 提示词模板设计:LangGPT的提示词设计框架
  • RK3288 android7.1 将普通串口设置为调试串口
  • WinUI3入门8:解决release版异常 取消优化和裁剪
  • QML革命:下一代GUI开发的核心优势详解
  • WebSocket 端点 vs Spring Bean
  • PyTorch 实现的 GlobalPMFSBlock_AP_Separate:嵌套注意力机制在多尺度特征聚合中的应用
  • LLM 编码器 怎么实现语义相关的 Token 向量更贴近? mask训练:上下文存在 ;; 自回归训练:只有上文,生成模型
  • 601N1 icm45696 串口python读取及显示
  • SQL Server2022版详细安装教程(Windows)
  • Flutter开发中记录一个非常好用的图片缓存清理的插件
  • MATLAB GUI界面设计 第四章——图像的绘制与显示
  • 项目上线(若依前后分离版)
  • Kubernetes安全
  • Frida Hook Android App 点击事件实战指南:从进程识别到成功注入
  • H5新增属性
  • C++ Vector 基础入门操作
  • 技能系统详解(2)——特效表现
  • nnv开源神经网络验证软件工具
  • 【第二章:机器学习与神经网络概述】03.类算法理论与实践-(1)逻辑回归(Logistic Regression)
  • 华大北斗TAU951M-P200单频定位模块 多系统冗余保障永不掉线 物流/车载导航首选
  • 历史项目依赖库Bugfix技巧-类覆盖
  • LED-Merging: 无需训练的模型合并框架,兼顾LLM安全和性能!!
  • Spring Boot:运用Redis统计用户在线数量
  • Flask学习笔记
  • 1.2、CAN总线帧格式
  • DeepSeek今天喝什么随机奶茶推荐器
  • Redis简介
  • 通过使用gitee发布项目到Maven中央仓库最新教程