GCC 编译四个阶段
从豆包里抄来的,记录一下。
总结:
第一阶段 预处理 生成.i文件
就是展开头文件和宏,去掉注释
就是把.c展开成点.i文件
第二阶段 编译 生成 .s汇编文件
把展开后的.i文件
编译成汇编文件
第三阶段 汇编 .s汇编文件,变成二进制文件.o
但是这个阶段的文件,还不能执行,因为
test.c 有用外部函数,printf的真实库地址,放进来。
第四阶段 就是 .o 文件,链接把libgcc,找到printf函数所在的库文件,把printf函数在库文件的地址
调用在.o文件中。
放在交叉编译平台上,大概是:
在Linux平台编译了arm程序
此时,它链接的是linux平台下的arm版本的库文件的函数地址,比如printf函数,所以在库的地址
然后把程序放到arm开发板,它会自定去找开发板的库,然后找到printf函数,在库文件(比如系统默认的运行路径)
然后找到printf函数在这个库文件的地址。
补充,链接的地址和运行程序时地址的区别。
编译链接时,:函数在 libc.so 这个文件里的「文件偏移」
运行程序时,系统把 libc.so 加载进内存,再算出「内存虚拟地址」
五、生活化比喻(一秒吃透)
把 libc.so 比作一本字典:
文件偏移:
printf 固定在字典 第 123 页(永远不变)
编译链接时:
只记下:printf 在 第 123 页
运行程序时:
系统把这本字典,随机放到你书房的 某张桌子上(内存基地址)
然后:
桌子起始位置 + 123 页 = 你真正拿到 printf 内容的实际位置
页码 = 库内文件偏移
桌子位置 = 内存映射基地址
最终拿到书的位置 = 运行时虚拟地址
创建 test.c:
#include <stdio.h>
#define MAX 100
int main() {
int num = MAX;
printf("num = %d\n", num);
return 0;
}
第一阶段:预处理(Preprocessing)
gcc -E test.c -o test.i
展开 #include 头文件
include <stdio.h> → 被替换成几百行标准库代码
展开 #define 宏定义
define MAX 100 → 代码中所有 MAX 变成 100
删除注释
处理条件编译 #if / #ifdef / #endif
最终得到一个超大的纯文本 .i 文件
第二阶段:编译(Compilation)
把预处理后的 C 代码 → 汇编代码
gcc -S test.i -o test.s
打开 test.s 会看到:
main:
pushq %rbp
movl $100, -4(%rbp)
movl -4(%rbp), %eax
...
第三阶段:汇编(Assembly)
把汇编代码 → 机器码(二进制)
gcc -c test.s -o test.o
文件特点:
二进制文件,无法直接打开阅读
包含函数、变量的符号表
未解析外部函数(如 printf 还没找到地址)
第四阶段:链接(Linking)
作用
把目标文件 + 依赖的库文件 → 最终可执行文件
这一步解决:
合并多个 .o 文件
找到并绑定外部函数(如 printf 来自 libc 标准库)
分配内存地址
生成操作系统可直接运行的格式(ELF/Linux、PE/Windows、Mach-O/macOS)
输入
.o 目标文件 + 系统库(libc.so 等)
输出
可执行文件(Linux 无后缀,Windows .exe)
问题:它已经是二进制,但还不能直接运行—— 因为还没和库函数链接(比如 printf) 什么意思解释
一句话把你讲透:.o 文件是二进制,为啥不能直接运行?
比如
printf("num = %d\n", num);
printf 不是你自己写的函数,是C 标准库 libc 里的现成函数,放在系统的:
/lib/x86_64-linux-gnu/libc.so.6 这种库里。
-
汇编生成 .o 文件时发生了什么?
编译器看到 printf:
知道这是一个外部函数
但不知道这个函数在内存哪个地址
所以只能在机器码里留一个空位置、记一个未解析符号
打个比方:
代码里写了:去【】找 printf
但现在【】是空的,没填地址
.o 里的二进制:
指令都生成好了,但调用 printf 的地址字段是空白占位,CPU 读到这里不知道跳去哪执行。 -
为什么不能直接运行?
操作系统运行一个程序,要求:
所有函数、变量都有确定的内存地址
所有外部调用(printf、sqrt 等)都要绑定到真实库地址
要整理程序段(代码段、数据段)、重定位地址
而 .o 文件:
有二进制机器码 ✅
但大量外部符号地址未填充 ❌
没有整理好可执行程序的内存布局 ❌
直接跑会立刻段错误 / 寻址失败,CPU 不知道跳转去哪执行 printf。 -
链接环节到底干了啥?(补坑)
链接器 ld 做两件关键事:
符号解析
找到 printf 在系统标准库 libc 里的真实内存地址。
重定位
把 .o 里所有空白占位的地址,全部填上真实地址。
相当于把刚才的:
去【____】找 printf
改成了:
去 0x7fxxxxxx 这个真实地址找 printf
填完所有空位、整理好内存布局,才是完整可执行文件。
编译前:
gcc -c test.c -o test.o
alientek@ubuntu16:~/Desktop/yzf/test$ nm test.o
0000000000000000 T main
U puts
编译后:
gcc test.o -o test
alientek@ubuntu16:~/Desktop/yzf/test$ nm test | grep puts
U puts@@GLIBC_2.2.5
浙公网安备 33010602011771号