预处理,编译,汇编,链接
预处理,编译,汇编,链接
第1步:预处理(Preprocessing)纯文本替换游戏,没有任何"编译"发生
预处理的本质:宏展开、头文件展开、条件编译(#ifdef)的裁剪、注释删除,纯粹是文本层面的操作,编译器这时候压根不懂什么是"变量类型"、"函数",只是个高级版的查找替换。头文件被展开了,几千行的内容
gcc -o main.i -E main.c
SEEKEND宏被替换成了2
第2步:编译(Compilation)—— 从C代码变成汇编代码
gcc -o main.s -S main.c这一步是真正的"编译":语法检查、语义检查(类型对不对、函数有没有声明)、生成对应的CPU指令。那些东西看到的汇编就是这一步产出来的。这时候还是纯文本文件,能用文本编辑器直接打开看。

第3步:汇编(Assembly)—— 从汇编代码变成机器码(二进制目标文件)
gcc -o main.o -c main.c用nm看这个目标文件里有哪些符号(函数名、变量名)
nm main.o
关键点来了:printf标记为U(undefined),因为printf的真正实现在libc库里,这个.o文件里只有一个"我要用这个符号,麻烦谁把它的地址填进来"的占位——这正是为什么这一步还不能直接运行,main.o是不完整的,缺一块。
第4步:链接(Linking)—— 把所有目标文件+库函数拼起来,补全所有地址
1.动态
gcc -o exe main.c我的系统默认用的是动态链接,printf这些函数压根没有在链接阶段被真正解析进你的可执行文件里,它们的地址是运行时才由动态链接器去找的。这也是为什么两份nm结果长得几乎一模一样。

确实是动态链接

libc.so.6就是标准C库的共享库(动态库)——printf、open、read的真正实现代码都在这个文件里,不在你的main里。

运行你的程序时,操作系统先加载main,发现里面调printf的地方是空的,于是加载/lib64/ld-linux-x86-64.so.2这个动态链接器,由它在程序真正开始跑之前,去libc.so.6里找到printf真正的地址,临时"打补丁"填进去——这个过程叫运行时重定位,跟之前理解的"链接阶段把地址焊死"是两码事
2.静态
链接把所有东西焊死
gcc -static -o main_static main.c文件体积暴涨50倍,因为这次链接器是真刀真枪地把printf、open、read……整个libc用到的那部分代码原样拷贝塞进了main_static这个文件里,不再是"运行时找libc.so要",而是"编译期直接焊死在自己身上"



