1.编译过程
1.1 预处理(Pre-Processing)
展开头文件, 宏替换(变量宏、函数宏)、替换空格等
gcc -E hello.c -o hello.i // -E 预处理选项, -o 重命名
1.2 编译(Compilation)
逐行检查程序中出现的语法错误,简单的逻辑错误
gcc -S hello.i -o hello.s
1.3 汇编(Assemble)
将 .s 汇编文件中所有的汇编指令翻译成二进制机器码(下面就是来了个截图,二进制显示了乱码)
gcc -c hello.s -o hello.o
1.4 链接(Linking)
将 .o 的目标文件,链接库文件、数据段合并,地址回填(把汇编里相对地址替换成程序运行后真正可以运行的地址)。生成可执行文件 hello
gcc hello.o -o hello
1.5 gcc常用参数
-E // 展开头文件 -s // 生成汇编文件 -c // 编译生成2进制文件 -I // 指定头文件路径 大i -L // 指定库文件路径 -l // 指定库名 小L -g // 包含调试信息 -On // n取 0-3, n 越大优化级别越高 -Wall //warning all 显示所有警告 -D // 编译时动态注入一个宏, 编译的时候控制#define的具体数值
2.动态库和静态库
2.1 函数库
本质:把具有相同功能的一组函数放到同一份文件中(源码或二进制的形式)
2.2 静态库
对执行速度有要求
- 机制:把引入的库文件通过编译直接复制到 .out 文件中
- 优点:将函数库中的函数本地化,寻址方便,速度快(函数库执行效率 = 自定义函数的执行效率)
- 缺点:每个程序都需要复制一份,会浪费内存
制作静态库:
gcc -c add.c sub.c mul.c // 制作 .o 文件 ar rs libmymath.a add.o sub.o mul.o // 制作静态库 gcc hello.c -L ./ -lmymath -o app // 指定头文件路径, 指定库名, 设置文件输出名app
2.3 动态库
对执行速度不敏感,对系统资源敏感;更新比较频繁(可以避免完全重新编译)
- 机制:代码共享
- 优点:节省内存(共享)、易于更新(动态链接)
- 缺点:相较于静态库函数调用速度慢,函数地址是延时绑定机制
gcc -c -fPIC add.c sub.c mul.c // -fPIC生成与位置无关的目标文件 gcc -shared -o libmymath.so sub.o mul.o add.o // 生成一个动态库 gcc hello.c -o app -L ./lib -l mymath -I ./inc // 生成文件
启动 ./app 报错,错误原因:”动态链接器“ ld-linux-x86-64.so.2 搜索动态库的路径没有指定
链接器:工作于 gcc 编译过程中的链接阶段。工作结束后生成可执行文件
动态链接器:工作于可执行程序运行之后,辅助加载器负责将动态库加载到内存
解决办法:
环境变量法:export LD_LIBRARY_PATH=./lib 将当前动态库所在的目录加入到环境变量,终端退出后环境变量就会失效
配置文件法:vi .bashrc 加入一行 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib,重启终端,每次启动终端自动生效
拷贝法:把自己的动态库直接拷贝到 /lib 或 /usr/lib 中
缓存文件法(推荐):修改配置文件,修改缓存文件,生成动态链接器需要搜索的新目录位置
sudo vim /etc/ld.so.conf // 修改动态链接库的路径 // 将绝对路径增加到文件中 sudo ldconfig -v // 更新 ld.so.cache 该文件影响动态链接库搜索的位置
3.makefile
3.1 语法
目标:依赖条件
tab缩进,指令
hello:hello.o add.o sub.o mul.o gcc hello.o add.o sub.o mul.o -o hello hello.o:hello.c gcc -c hello.c -o hello.o sub.o:sub.c gcc -c sub.c -o sub.o add.o:add.c gcc -c add.c -o add.o mul.o:mul.c gcc -c mul.c -o mul.o
模式规则:在makefile中,具有有严格统一的规则,使用模式规则代替,模式规则中只能使用 $
%.o:%.c
gcc -c $
静态模式规则:将模式规则指定给某一个变量使用
$(obj):%.o:%.c
gcc -c $
伪目标:针对残缺的规则,使之生成目标(比如有一个clean.o文件已经是最新的,就不会执行clean清除命令
.PHONY:clean ALL
3.2 函数介绍
3.2.1 wildcard 函数
// 匹配当前工作目录下的所有 .c 文件,将文件名组成列表,赋值给 src 变量 // src = add.c sub.c mul.c hello.o src = $(wildcard ./*.c)
3.2.2 patsubst 函数
// 用来替换参数, 将 参数3 中包含 参数1 的部分替换为 参数2 // obj = add.o sub.o mul.o hello.o obj = $(patsubst %.c, %.o, $(src))
3.3 普通变量和自动变量
普通变量
- 定义变量语法:变量名 = 变量值 (foo = abc)
- 取变量值语法:$(变量) (bar = $abc —> bar = abc)
自动变量
- $@: 在规则的命令中,表示规则中的目标
- $^ : 在规则的命令中,表示所有依赖条件
- $
3.4 关键字
ALL: 默认情况下第一组生成文件的目标就是终极目标,或者显示的写ALL:hello表示终极目标,完成后结束makefile
clean: 借助makefile清除项目中的指定文件,例如(clean: -rm -rf $(obj) hello
指令 make -n 模拟执行命令,不真正执行,可以先看一次避免出问题
3.5 修改后的 makefile
src = $(wildcard *.c) obj = $(patsubst %.c, %.o, $(src)) CC = gcc target = app ALL:$(target) $(target):$(obj) $(CC) $^ -o $@ $(obj):%.o %.c $(CC) -c $o $@ clean: -rm -rf $(obj) $(target) .PHONY:clean ALL
4. gdb调试
4.1 基础指令
基础的断点设置和继续运行等指令
- -g:使用该指令编译可执行文件,否则没有调试表
- gdb ./a.out
- list:list 1 列出源码,根据源码指定行号设置断点,1表示从第一行开始显示源码
- b:b 55 在第55行添加断点
- run/r:运行程序,启动调试(可以直接找到停止位置就是出错位置)
- next/n:下一条指令(越过函数)
- step/s:下一条执行(进入函数内部)
- print/p:打印变量值,如p var 查看 var 变量的值
- continue:执行到下一个断点
- finish:结束当前函数调用
- quit:退出调试
- start:不使用断点,直接开始单步调试
设置一些条件和查看栈帧及变量
- set args:启动gdb调试后,通过该指令可以设置main函数的参数,需要在start和run指令之前设置
- info b:查看当前断点信息表
- b 23 if i=5:设置断点在23行,如果 i=5 时断点才生效
- ptybe:查看变量类型
- display:设置跟踪变量,display i,跟踪变量 i
- undisplay:取消跟踪变量
- bt:列出当前程序存活的栈帧
- frame:如果有多层调用,在内层想查看外层栈帧里的参数,使用 frame n (n 是栈帧编号)切换栈帧,再使用 p var 打印变量