目录
- 一、编译器gcc/g++的使用
-
- [1. 四大编译阶段](#1. 四大编译阶段)
- [2. 动静态库链接(了解)](#2. 动静态库链接(了解))
- 二、调试器gdb的使用
- 三、Linux项目自动化构建工具make/Makefile
-
- [1. make和makefile是什么?](#1. make和makefile是什么?)
- [2. makefile语法结构](#2. makefile语法结构)
- [3. make的核心工作原理](#3. make的核心工作原理)
一、编译器gcc/g++的使用
1. 四大编译阶段
- 预处理:进行宏替换,头文件展开,去注释,条件编译等
- 编译:语法/语义的检查,生成汇编代码
- 汇编:汇编器将汇编转为机器码(二进制目标文件)
- 链接:合并目标文件,解析库引用,形成可执行程序
| 阶段 | 操作 | 说明 |
|---|---|---|
| 预处理 | gcc -E test.c -o test.i |
从当前文件test.c开始进行程序的翻译,预处理完成就停下形成.i文件 |
| 编译 | gcc -S test.i -o test.s |
从当前文件test.i开始进行程序的编译,编译完成就停下,形成.s文件 |
| 汇编 | gcc -c test.s -o test.o |
从当前文件test.s开始进行程序的翻译,汇编完成就停下,形成.o文件 |
| 链接 | gcc test.o -o my.exe |
从当前目标文件test.o开始编译形成可执行程序my.exe |
表中形成.iso文件时,起点文件都可以是源文件test.c
指令解释:
bash
#.c/.cpp -> .i -> .s -> .o -> 可执行程序
-E #只预处理 .i
-S #只编译到汇编 .s
-c #只汇编到目标文件 .o
-o #指定输出文件的名称(起名字)
gcc test.c -o my.exe #将源文件test.c直接编译成可执行程序my.exe
2. 动静态库链接(了解)
静态库:后缀为.a,命名格式为 libxxx .a(例如:libc.a 是C标准库静态版)
动态库:后缀为.so,命名格式为 libxxx .so(例如:libc.so 是C标准库动态版)
链接:是把多个.o文件+库文件合并,最终编译成可执行文件
动静态库都只在链接阶段参与编译过程,区别在于连接器对于它们的处理方式
- 静态库链接
链接时机:在链接阶段就完成全部的拷贝动作
核心动作:链接器会把静态库中你程序调用的代码,直接拷贝进你的可执行文件里
结果:造成可执行文件的体积变大,运行时不在需要.a文件,因为已经拷贝进可执行文件中了,也不再依赖外部库
bash
gcc test.c -o test_static -static #带上-static选项强制让链接器优先使用静态库
- 动态库链接
链接时机:链接阶段只 "登记",运行时才加载
2.1 编译链接阶段:
- 检查库中函数的声明是否匹配,保证语法正确
- 在可执行文件里记录依赖信息(需要的库),不拷贝任何库代码
2.2 程序运行阶段:加载所依赖的库,由系统的动态链接器,根据可执行文件里的记录,找到并加载.so文件到内存中,多个程序可以共享同一份库代码,节省内存
bash
gcc test.c -o mytest.exe #不加任何选项,gcc默认优先使用动态库
- 动静态库链接的对比
| 特性 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 编译阶段的链接行为 | 直接把库代码拷贝进可执行文件中 | 只记录依赖关系,不拷贝代码 |
| 库文件参与阶段 | 仅参与链接阶段 | 参与链接阶段+运行阶段 |
| 运行时是否需要库文件 | 不需要,可执行文件独立 | 必须存在,否则程序无法启动 |
| 可执行文件的体积 | 大 | 小 |
| 库更新影响 | 库更新后,程序需要重新编译 | 替换库文件即可,程序无需重新编译 |
二、调试器gdb的使用
- 前提:gdb调试需要调试信息,所以在编译命名必须加
-g
bash
gcc test.c -o testexe -g #带上-g选项才会有调试信息,不加的话,gdb看不到代码行,变量,没法调试
gdb ./testexe #启动调试程序
- 核心调试流程
打断点 -> 运行 -> 单步运行 -> 查变量 -> 继续/退出
断点操作
b 行号 # 在指定行打断点(例如:b/break 5)
b 函数名 # 在函数开头打断点(例如:b/break main)
info b # 查看所有断点(例如:info b/break)
d 断点编号 # 删除断点(例如:delete/del/d 5)
disable 断点编号 # 临时禁用该断点 (例如:disable 5)
enable 断点编号 # 重新启用被禁用的断点(例如:enable 5)
变量的查看与修改
p 变量名 # 打印变量当前的值 p/print 变量名 (例如:p/print a)
p 变量名 = 新值 # p a=100 把变量a的值改成100
#也可以调用函数查看返回值
p func(参数) # p add(1,2)调用add函数,传入1,2查看返回值
dispaly 变量名 # 每次程序暂停(单步,断点)时,自动显示该变量的值,该功能等价于vs的监视窗口查看变量的动态变化(例如:display a)
undisplay # 取消所有的变量跟踪
undisplay 编号 #取消指定编号的变量跟踪(编号可通过info display查看)
info locals # 查看当前栈里面所有的局部变量的值
辅助操作
r/run # 运行
l/list # 查看代码,默认查看前10行,要查看全部代码一直按回车即可
l/list 行号 # 显示当前行附近的代码
l/list 函数名 #列出函数的源代码
n/next # 单条执行,不进入函数内部
s/step # 单步执行,进入函数内部
finish # 执行到当前函数返回,然后停下来等待命令
q/quit # 退出调试
Crtl + d # 退出调试
示例:解释finish的用法
c
//finish使用的场景:
int add(int x,int y)
{
return x+y;
}
int main()
{
int sum=0;
sum=add(1,2);
printf("sum=%d",sum);
return 0;
}
当单步执行到add函数内部时,此时使用finish可以执行完add函数返回它的结果,并停留在调用处的下一行

总结:finish的功能是:执行完当前函数的剩余代码,返回到调用它的上一层函数,并暂停。它帮你跳过了函数内部剩下的所有步骤,不用再一步步 n/s 往下走。同时还会自动告诉你函数的返回值,非常方便。
三、Linux项目自动化构建工具make/Makefile
1. make和makefile是什么?
make :是一个命令行工具,用来自动化构建项目,根据makefile里面的规则,自动执行编译、链接、清理等操作
makefile/Makefile :是一个文本文件,定义了项目的目标、依赖关系和构建命令,让make知道这么生成目标文件
核心作用:自动化构建+增量编译(只编译修改过的文件,提高效率)
2. makefile语法结构
基本规则
makefile
目标文件: 依赖文件列表
构建命令(必须以Tab键开头)
执行命令
make #默认执行第一个目标
make 目标名 #执行指定目标(例如:make clean)
例如:
bash
mytest : test.c
gcc -o mytest test.c
.PHONY : clean
clean:
rm -f mytest
mytest:目标文件(可执行程序的名字)
test.c:依赖文件(生成目标文件所需要的源文件)
gcc -o mytest test.c:构建命令(如何从依赖生成目标)
.PHONY:clean:声明clean是伪目标,避免和当前目录下名为clean的文件冲突
clean:伪目标,用来清理文件,不生成任何实际文件
rm -f mytest:伪目标的下的操作
tips :.PHONY:clean就是告诉make,clean不是文件,是一个伪目标,每次执行make clean都必须执行对应的命令,不管有没有同名文件
3. make的核心工作原理
-
增量编译的核心:时间戳对比
make会比较目标文件和依赖文件的最后修改时间:
- 如果目标文件不存在,则执行命令生成目标
- 如果目标文件存在,但依赖文件的修改时间比目标文件更新。则执行命令重新生成目标文件
- 如果目标文件存在,且所有依赖文件都比目标文件旧。则不执行任何命令,直接跳过
这就是为什么修改了.c文件后,make会重新编译,没修改时,make不会重新编译,大幅度提高效率
-
依赖项的自动推导
make会从目标文件出发,递归解析依赖关系,构建出完整的依赖链,自动按顺序执行所有必要的构建步骤
示例:


make会自动按照依赖顺序执行,一步到位生成可执行文件,但有个前提,目标文件必须是可执行文件的名字,后面的顺序无所谓,目标必须要对 -
简化写法
用变量简化重复的代码,让makefile更容易维护
版本1:
bash
testexe : test.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f testexe
版本2:
bash
bin = testexe
src = test.c
$(bin):$(src)
gcc -o $@ $^
.PHONY:clean
clean:
rm -f $(bin)
$@:表示当前规则的目标文件(也就是$(bin),说白了就是testexe)
$^:表示依赖文件(也就是$(src),说白了就是test.c)
用变量的形式写,修改起来会很方便