Linux —《开发三件套:gcc/g++、gdb、make/Makefile 全解析》

目录

一、编译器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文件+库文件合并,最终编译成可执行文件

动静态库都只在链接阶段参与编译过程,区别在于连接器对于它们的处理方式

  1. 静态库链接
    链接时机:在链接阶段就完成全部的拷贝动作
    核心动作:链接器会把静态库中你程序调用的代码,直接拷贝进你的可执行文件里
    结果:造成可执行文件的体积变大,运行时不在需要.a文件,因为已经拷贝进可执行文件中了,也不再依赖外部库
bash 复制代码
gcc test.c -o test_static -static #带上-static选项强制让链接器优先使用静态库
  1. 动态库链接

链接时机:链接阶段只 "登记",运行时才加载

2.1 编译链接阶段:

  • 检查库中函数的声明是否匹配,保证语法正确
  • 在可执行文件里记录依赖信息(需要的库),不拷贝任何库代码

2.2 程序运行阶段:加载所依赖的库,由系统的动态链接器,根据可执行文件里的记录,找到并加载.so文件到内存中,多个程序可以共享同一份库代码,节省内存

bash 复制代码
gcc test.c -o mytest.exe #不加任何选项,gcc默认优先使用动态库
  1. 动静态库链接的对比
特性 静态库(.a) 动态库(.so)
编译阶段的链接行为 直接把库代码拷贝进可执行文件中 只记录依赖关系,不拷贝代码
库文件参与阶段 仅参与链接阶段 参与链接阶段+运行阶段
运行时是否需要库文件 不需要,可执行文件独立 必须存在,否则程序无法启动
可执行文件的体积
库更新影响 库更新后,程序需要重新编译 替换库文件即可,程序无需重新编译

二、调试器gdb的使用

  1. 前提:gdb调试需要调试信息,所以在编译命名必须加-g
bash 复制代码
gcc test.c -o testexe -g #带上-g选项才会有调试信息,不加的话,gdb看不到代码行,变量,没法调试
gdb ./testexe 	#启动调试程序
  1. 核心调试流程
    打断点 -> 运行 -> 单步运行 -> 查变量 -> 继续/退出

断点操作

复制代码
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的核心工作原理

  1. 增量编译的核心:时间戳对比

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

    • 如果目标文件不存在,则执行命令生成目标
    • 如果目标文件存在,但依赖文件的修改时间比目标文件更新。则执行命令重新生成目标文件
    • 如果目标文件存在,且所有依赖文件都比目标文件旧。则不执行任何命令,直接跳过
      这就是为什么修改了.c文件后,make会重新编译,没修改时,make不会重新编译,大幅度提高效率
  2. 依赖项的自动推导

    make会从目标文件出发,递归解析依赖关系,构建出完整的依赖链,自动按顺序执行所有必要的构建步骤

    示例:


    make会自动按照依赖顺序执行,一步到位生成可执行文件,但有个前提,目标文件必须是可执行文件的名字,后面的顺序无所谓,目标必须要对

  3. 简化写法

    用变量简化重复的代码,让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)

用变量的形式写,修改起来会很方便

相关推荐
何中应2 小时前
Grafana如何给列表设置别名
运维·grafana·监控
senijusene2 小时前
基于 imx6ull平台按键驱动开发:input子系统+中断子系统+platform总线
linux·驱动开发
MXsoft6182 小时前
运维的尽头,是把“救火”变成“算命”
运维
大卡片2 小时前
IO模型与并发服务器设计
运维·服务器·网络
莎士比亚的文学花园2 小时前
Linux驱动开发(1)——系统移植
linux·运维·服务器
PH = 73 小时前
OverlayFS联合文件系统使用示例
java·linux·服务器
AC赳赳老秦3 小时前
OpenClaw进阶技巧:批量修改文件内容、替换关键词,解放双手
java·linux·人工智能·python·算法·测试用例·openclaw
志栋智能3 小时前
超自动化巡检:解锁运维数据的深层价值
运维·服务器·数据库·自动化
Joseph Cooper4 小时前
STM32MP157 Linux驱动学习笔记(四):典型总线与设备模型(SPI/USB)
linux·stm32·学习