1.编译过程

1.1 预处理(Pre-Processing)

  展开头文件, 宏替换(变量宏、函数宏)、替换空格等

1
gcc -E hello.c  -o hello.i     // -E 预处理选项, -o 重命名

img

1.2 编译(Compilation)

  逐行检查程序中出现的语法错误,简单的逻辑错误

1
gcc -S hello.i -o hello.s

img

1.3 汇编(Assemble)

  将 .s 汇编文件中所有的汇编指令翻译成二进制机器码(下面就是来了个截图,二进制显示了乱码)

1
gcc -c hello.s -o hello.o

img

1.4 链接(Linking)

  将 .o 的目标文件,链接库文件、数据段合并,地址回填(把汇编里相对地址替换成程序运行后真正可以运行的地址)。生成可执行文件 hello

1
gcc hello.o -o hello

1.5 gcc常用参数

1
2
3
4
5
6
7
8
9
10
-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 文件中
  • 优点:将函数库中的函数本地化,寻址方便,速度快(函数库执行效率 = 自定义函数的执行效率)
  • 缺点:每个程序都需要复制一份,会浪费内存

  制作静态库:

1
2
3
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 动态库

  对执行速度不敏感,对系统资源敏感;更新比较频繁(可以避免完全重新编译)

  • 机制:代码共享
  • 优点:节省内存(共享)、易于更新(动态链接)
  • 缺点:相较于静态库函数调用速度慢,函数地址是延时绑定机制
1
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 中

  缓存文件法(推荐):修改配置文件,修改缓存文件,生成动态链接器需要搜索的新目录位置

1
2
3
sudo vim /etc/ld.so.conf    // 修改动态链接库的路径
// 将绝对路径增加到文件中
sudo ldconfig -v // 更新 ld.so.cache 该文件影响动态链接库搜索的位置

3.makefile

3.1 语法

 目标:依赖条件

 tab缩进,指令

1
2
3
4
5
6
7
8
9
10
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中,具有有严格统一的规则,使用模式规则代替,模式规则中只能使用 $< 符号

1
2
%.o:%.c
gcc -c $< -o $@

 静态模式规则:将模式规则指定给某一个变量使用

1
2
$(obj):%.o:%.c
gcc -c $< -o $@

 伪目标:针对残缺的规则,使之生成目标(比如有一个clean.o文件已经是最新的,就不会执行clean清除命令

1
.PHONY:clean ALL

3.2 函数介绍

3.2.1 wildcard 函数

1
2
3
// 匹配当前工作目录下的所有 .c 文件,将文件名组成列表,赋值给 src 变量
// src = add.c sub.c mul.c hello.o
src = $(wildcard ./*.c)

3.2.2 patsubst 函数

1
2
3
// 用来替换参数, 将 参数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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 打印变量