面试官问 Linux 编译调试?gcc 编译流程 + gdb 断点调试 + git 版本控制,连 Makefile 都标好了

✨ 孤廖: 个人主页
🎯 个人专栏: 《C++:从代码到机器》
🎯 个人专栏: 《Linux系统探幽:从入门到内核》
🎯 个人专栏: 《算法磨剑:用C++思考的艺术》
折而不挠,中不为下

文章目录
- 前言:
- 正文:
- 编译器gcc/g++
- 预处理(宏替换)
- 编译(⽣成汇编)
- 汇编(⽣成机器可识别代码)
- 链接(⽣成可执⾏⽂件或库⽂件)
- 动态链接和静态链接
- 静态库和动态库
- ⾃动化构建-make/Makefile
- Linux第⼀个系统程序−进度条
- 版本控制器Git
- 调试器 - gdb/cgdb使⽤
- 常⻅使⽤
- 结语:
前言:
Linux 的基础开发工具 继上篇的yum(软安装包) vim(编辑器),我们还剩gcc/g++ (编译器) gdb (调试器) git (版控制器) 该篇主要讲述这三个工具的使用 以完善Linux 的基础开发工具的体系
正文:
编译器gcc/g++
一个程序从源文件形成可执行程序 共分为四步
- 预处理(进⾏宏替换/去注释/条件编译/头⽂件展开等)
- 编译(⽣成汇编)
- 汇编(⽣成机器可识别代码)
- 链接(⽣成可执⾏⽂件或库⽂件)
格式 gcc [选项] 要编译的⽂件 [选项] [⽬标⽂件]
预处理(宏替换)
- 预处理功能主要包括宏定义,⽂件包含,条件编译,去注释等。
- 预处理指令是以#号开头的代码⾏。
- 编译选项:
格式 gcc [选项] 要编译的⽂件 [选项] [⽬标⽂件]
eg:gcc a.c -E a.i //预处理阶段
gcc a.i - S a.s //编译阶段 生成汇编 该选项的作⽤是让 gcc 在预处理结束后停⽌编译过程。
gcc a.s -c a.o // 汇编 生成机器可识别代码(01二进制语言)
- 选项“-o”是指⽬标⽂件,“.i”⽂件为已经过预处理的C原始程序," .s " 指已生成汇编语言文件
这三步的指令选项 的记忆技巧 可以看键盘的左上角 ESC 不过注意细节S E 大写 c 小写
编译(⽣成汇编)
- 在这个阶段中,gcc ⾸先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的⼯作,在检查⽆误后,gcc 把代码翻译成汇编语⾔。
- ⽤⼾可以使⽤“-S”选项来进⾏查看,该选项只进⾏编译⽽不进⾏汇编,⽣成汇编代码。
实例: gcc –S hello.i –o hello.s
汇编(⽣成机器可识别代码)
- 汇编阶段是把编译阶段⽣成的“.s”⽂件转成⽬标⽂件
- 读者在此可使⽤选项“-c”就可看到汇编代码已转化为“.o”的⼆进制⽬标代码了
- 实例: gcc –c hello.s –o hello.o
链接(⽣成可执⾏⽂件或库⽂件)
在成功汇编之后,就进⼊了链接阶段。
实例: gcc hello.o –o hello
动态链接和静态链接
在我们的实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数,
但是每个源⽂件都是独⽴编译的,即每个.c⽂件会形成⼀个.o⽂件,为了满⾜前⾯说的依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序**。这个链接的过程就是静态链接
静态链接缺点:
浪费空间:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对同⼀个⽬标⽂件都有依赖,如多个程序中都调⽤了printf()函数,则这多个程序中都含有printf.o,所以同⼀个⽬标⽂件都在内存存在多个副本;更新⽐较困难:因为每当库函数的代码修改了,这个时候就需要重新进⾏编译链接形成可执⾏程序。但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东西,在执⾏的时候运⾏速度快
为了解决静态链接的问题 前辈们提出了动态链接的概念并很好的设计这一过程
- 动态链接的基本思想是把程序按照模块拆分成各个相对独⽴部分,在
程序运⾏时才将它们链接在⼀起形成⼀个完整的程序,⽽不是像静态链接⼀样把所有程序模块都链接成⼀个单独的可执⾏⽂件。
下面我们查看一个程序 可以看到该可执行程序依赖一个C动态链接库

ldd 命令⽤于打印程序或者库⽂件所依赖的共享库列表
上面我们涉及到了 ”库“这一重要概念
- 我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,⽽没有定义函数的实现,那么,是在哪⾥实“printf”函数的呢?
- 最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库⽂件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进⾏查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,⽽这也就是链接的作⽤
静态库和动态库
- 静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。其后缀名⼀般为“.a
- 动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程序执⾏时由运⾏时链接⽂件加载库,这样可以节省系统的开销。动态库⼀般后缀名为“.so”,如前⾯所述的libc.so.6 就是动态库。gcc 在编译时默认使⽤动态库。完成了链接之后,gcc 就可以⽣成可执⾏⽂件,如下所⽰。 gcc hello.o –o hello
- gcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过 file 命令验证
- Linux下,动态库XXX.so, 静态库XXX.a
- Windows下,动态库XXX.dll, 静态库XXX.lib
这里需要需要注意的是 我们的云服务器一般是没有安装C/C++的静态库的(因为默认采用动态链接),可以采用以下指令安装
# Centos
yum install glibc-static libstdc++-static -y
eg:

⾃动化构建-make/Makefile
- 会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒
- ⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作
- makefile带来的好处就是⸺“⾃动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。
- make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具.`⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可⻅,makefile都成为了⼀种在⼯程⽅⾯的编译⽅法。
make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。
SRC=$(wildcard *.c)
BIN=proc.exe
OBJ=$(SRC:.c=.o)
RM =rm -r$(BIN):$(OBJ)gcc -o $@ $^%.o:$.cgcc -c $<.PHNOY:cleanclean:$(RM) $(OBJ) $(BIN)
详细解释:

makefile 最重要的就是这三步
- 依赖关系
上面的所有的可执行文件BIN依赖于所有的OBJ文件
- 依赖方法:
gcc -o $@ $^ 就是 依赖方法
$^指向被依赖对象OBJ $@指向BIN
- 项⽬清理
- ⼯程是需要被清理的
- 像clean这种,没有被第⼀个⽬标⽂件直接或间接关联,那么它后⾯所定义的命令将不会被⾃动执⾏,不过,我们可以显⽰要make执⾏。即命令⸺“make clean”,以此来清除所有的⽬标⽂件,以便重编译。
只有Make file 的第一个依赖 才可以直接使用make 默认执行
- 但是⼀般我们这种clean的⽬标⽂件,我们将它设置为伪⽬标,⽤
.PHONY修饰,伪⽬标的特性是,总是被执⾏的

Linux第⼀个系统程序−进度条
前提需要知道回车和换行的区别以及 缓冲区的概念 这里不做补充 自行查阅学习
【代码展示】:
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define NUM 101
#define STYLE '='
// vesion1
void process_v1()
{char buffer[NUM];memset(buffer, 0, sizeof(buffer));const char *lable="|/-\\";int len = strlen(lable);int cnt = 0;while(cnt <= 100){printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt%len]);fflush(stdout);buffer[cnt]= STYLE;cnt++;usleep(50000);}printf("\n");
}
// verison2
void FlushProcess(double total, double current)
{char buffer[NUM];memset(buffer, 0, sizeof(buffer));const char *lable="|/-\\";int len = strlen(lable);static int cnt = 0;
// 不需要⾃⼰循环,填充#int num = (int)(current*100/total); // 11.0 / 1000int i = 0;for(; i < num; i++){buffer[i] = STYLE;}double rate = current/total;cnt %= len;printf("[%-100s][%.1f%%][%c]\r", buffer, rate*100, lable[cnt]);cnt++;fflush(stdout);
}
double total = 1024.0;
double speed = 1.0;
void DownLoad()
{double current = 0;while(current <= total){FlushProcess(total, current);// 下载代码usleep(3000); // 充当下载数据current += speed;
}printf("\ndownload %.2lfMB Done\n", current);
}
int main()
{DownLoad();return 0;
}
版本控制器Git
正常使用会三板斧就行了
- git add
- git commit ,
- git push
如果想要详细的企业级开发工具Git 可以参考我之前所写的两篇文章
上:萌系学 Git:从提交规范到团队协作,工具喵教你玩转企业级工具
下:萌系学 Git:从提交规范到团队协作,工具喵教你玩转企业级工具
这两篇文章详细的介绍了 Git的发展历程 以及使用和企业级开发流程
调试器 - gdb/cgdb使⽤
- 程序的发布⽅式有两种, debug 模式和 release 模式, Linux gcc/g++ 出来的⼆进制程序,默认是 release 模式。
- 要使⽤gdb调试,必须在源代码⽣成⼆进制程序的时候, 加上 -g 选项,如果没有添加,程序⽆法被编译
gcc a.c -o a.out //releasegcc a.c -o a.out -g //debug\
常⻅使⽤
- 开始: 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 10 break 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 行号 | 执行到指定行 | until 20 |
backtrace/bt | 查看当前执行栈的各级函数调用及参数 | backtrace |
info/i locals | 查看当前栈帧的局部变量值 | info locals |
quit | 退出GDB调试器 | quit |
📌 安装cgdb:
• 上⾯的基本调试还是⿇烦,虽然是⿊屏,但是还是想看到代码调试
• 推荐安装cgdb:
• Ubuntu: sudo apt-get install -y cgdb
• Centos: sudo yum install -y cgdb
cgdb下的调试 : 增加断点 查看 断点 删除断点 查看函数 逐过程 逐语句 运行 监视变量 监视内存 等等 仿照VS 自行尝试
结语:
从 gcc 编译的 “四步拆解” 到 gdb 调试的 “断点魔法”,再到 git 版本控制的 “代码托管”,这些工具是 Linux 开发的 “基石套装”。若对编译链接原理、Makefile 语法还存疑,不妨多跑几遍示例代码;想进一步深入,也可以尝试用静态库 / 动态库优化自己的项目。
如果觉得本篇文章对你有所帮助的话 可以点赞加收藏 ,如果还有技术或者疑惑的话 可以在评论区里交流

