编译-链接过程备忘录
目录
1. 静态库(.a / .lib)—— 不是链接器生成的
2. 动态库(.so / .dylib / .dll)—— 由链接器生成
3. 可执行文件(app / exe)—— 由链接器生成
4.动态库内部藏着两张“符号表”
5,为什么 strip“通常”没事?(以及“有事”的例外)
1. 静态库(.a / .lib)—— 不是链接器生成的
生成工具:归档器(ar),而不是链接器(ld)。
过程:编译器先把源码编译成目标文件(.o / .obj),然后
ar命令把这些 .o 文件打包成一个静态库文件。这一步只是“打包”,不进行符号解析和重定位(即没有“连接”过程)。命令:
ar rcs libmylib.a file1.o file2.o
2. 动态库(.so / .dylib / .dll)—— 由链接器生成
生成工具:链接器(ld),带上特殊参数(如
-shared或-dynamiclib)。过程:链接器将多个 .o 文件或依赖的静态库进行部分链接。它会解析内部符号,但会故意保留外部依赖符号为“未定义”,生成一个可重定位且位置无关(PIC)的共享对象文件。
命令:
gcc -shared -o libmylib.so file1.o file2.o(后台实际调用 ld)
3. 可执行文件(app / exe)—— 由链接器生成
生成工具:链接器(ld),默认模式。
过程:链接器将所有 .o 文件、静态库(复制代码)、动态库(登记导入表)进行完整链接。这一步会把所有静态符号的地址固定下来(或留作重定位),生成操作系统可以直接加载运行的文件格式(ELF / PE / Mach-O)。
命令:
gcc -o myapp main.o -lmylib(后台调用 ld)
直观变换流程:
源码编译:
.c/.cpp——(编译器 cc1plus)——>.o目标文件(二进制机器码,但地址是乱的)打包成静态库:
.o文件们——(ar 归档器)——>.a静态库(相当于一个 .o 的压缩包,没有链接)链接生成动态库:
.o/.a——(ld 链接器 + -shared)——>.so动态库(半链接状态,保留外部跳转钩子)链接生成可执行文件:
.o/.a/.so——(ld 链接器,默认)——>app可执行文件(全链接状态,具备运行入口)
4.动态库内部藏着两张“符号表”
第一张:动态符号表(
.dynsym)——这是命根子这张表记录了对外公开的函数名(比如
int add(int a, int b))。作用:当你的可执行文件或其他库要调用这个库时,动态链接器就靠这张表里的名字去查找地址。如果这张表没了,外部程序就找不到入口,加载时会直接报错“undefined symbol”。
第二张:调试符号表(
.symtab+ 行号信息)——这是说明书这张表记录了变量名、源码文件名、代码在第几行、甚至函数参数名。
作用:仅用于 GDB 调试、反汇编时看函数名、或者崩溃时打印堆栈。运行时完全不需要它。
5,为什么 strip“通常”没事?(以及“有事”的例外)
标准 strip 行为(Linux):大多数 Linux 上的
strip命令默认只删除调试符号表(第二张),而保留动态符号表(第一张)。所以你在 Linux 上直接strip libxxx.so,库能正常跑,因为命根子还在。这叫“安全瘦身”。
