第四章:静态链接
4.1 空间与地址分配
空间分配
静态链接时,链接器需要为每个目标文件分配空间。主要包括:
- 代码段空间
- 数据段空间
- BSS 段空间
示例代码:
1 2 3 4 5 6 7 8
| #include <stdio.h>
int a = 10; int b = 20;
void func_a() { printf("a = %d, b = %d\n", a, b); }
|
1 2 3 4 5 6 7 8
| #include <stdio.h>
int c = 30; int d = 40;
void func_b() { printf("c = %d, d = %d\n", c, d); }
|
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h>
extern void func_a(); extern void func_b();
int main() { func_a(); func_b(); return 0; }
|
编译和链接命令:
1 2 3 4 5 6 7 8 9 10
| gcc -c static_a.c -o static_a.o gcc -c static_b.c -o static_b.o gcc -c static_main.c -o static_main.o
gcc static_a.o static_b.o static_main.o -o static_program
objdump -h static_program
|
地址分配
链接器需要为每个符号分配地址。主要包括:
- 代码段地址
- 数据段地址
- BSS 段地址
使用以下命令查看地址分配:
1 2 3 4 5
| nm static_program
readelf -S static_program
|
符号解析
链接器需要解析所有符号的引用。主要包括:
- 全局符号解析
- 局部符号解析
- 弱符号解析
示例代码:
1 2 3 4 5 6 7 8
| #include <stdio.h>
int global_var = 10; static int static_var = 20;
void func_a() { printf("global_var = %d, static_var = %d\n", global_var, static_var); }
|
1 2 3 4 5 6 7 8
| #include <stdio.h>
extern int global_var; static int static_var = 30;
void func_b() { printf("global_var = %d, static_var = %d\n", global_var, static_var); }
|
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h>
extern void func_a(); extern void func_b();
int main() { func_a(); func_b(); return 0; }
|
编译和链接命令:
1 2 3 4 5 6 7 8 9 10
| gcc -c symbol_resolve_a.c -o symbol_resolve_a.o gcc -c symbol_resolve_b.c -o symbol_resolve_b.o gcc -c symbol_resolve_main.c -o symbol_resolve_main.o
gcc symbol_resolve_a.o symbol_resolve_b.o symbol_resolve_main.o -o symbol_resolve_program
nm symbol_resolve_program
|
4.2 符号解析与重定位
符号解析
符号解析的过程:
- 收集所有目标文件的符号
- 建立符号表
- 解析符号引用
示例代码:
1 2 3 4 5 6
| #include <stdio.h>
int a = 10; void func_a() { printf("a = %d\n", a); }
|
1 2 3 4 5 6
| #include <stdio.h>
extern int a; void func_b() { printf("a = %d\n", a); }
|
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h>
extern void func_a(); extern void func_b();
int main() { func_a(); func_b(); return 0; }
|
编译和链接命令:
1 2 3 4 5 6 7 8 9 10
| gcc -c resolve_a.c -o resolve_a.o gcc -c resolve_b.c -o resolve_b.o gcc -c resolve_main.c -o resolve_main.o
gcc resolve_a.o resolve_b.o resolve_main.o -o resolve_program
nm resolve_program
|
重定位
重定位的过程:
- 计算符号的最终地址
- 修改符号引用
- 更新重定位表
示例代码:
1 2 3 4 5 6
| #include <stdio.h>
int a = 10; void func_a() { printf("a = %d\n", a); }
|
1 2 3 4 5 6
| #include <stdio.h>
extern int a; void func_b() { printf("a = %d\n", a); }
|
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h>
extern void func_a(); extern void func_b();
int main() { func_a(); func_b(); return 0; }
|
编译和链接命令:
1 2 3 4 5 6 7 8 9 10 11 12
| gcc -c relocate_a.c -o relocate_a.o gcc -c relocate_b.c -o relocate_b.o gcc -c relocate_main.c -o relocate_main.o
gcc relocate_a.o relocate_b.o relocate_main.o -o relocate_program
objdump -r relocate_a.o objdump -r relocate_b.o objdump -r relocate_main.o
|
重定位表
重定位表记录了需要重定位的位置。主要包括:
- 重定位类型
- 重定位符号
- 重定位地址
使用以下命令查看重定位表:
1 2 3 4 5
| readelf -r relocate_program
readelf -S relocate_program
|
实践练习
- 空间分配实验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| cat > space_test.c << EOL #include <stdio.h>
int global_var = 10; static int static_var = 20;
void func() { printf("global_var = %d, static_var = %d\n", global_var, static_var); }
int main() { func(); return 0; } EOL
gcc -c space_test.c -o space_test.o
objdump -h space_test.o
|
- 符号解析实验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| cat > resolve_test.c << EOL #include <stdio.h>
extern int global_var; void func() { printf("global_var = %d\n", global_var); }
int main() { func(); return 0; } EOL
gcc -c resolve_test.c -o resolve_test.o
nm resolve_test.o
|
- 重定位实验
1 2 3 4 5
| objdump -r resolve_test.o
readelf -S resolve_test.o
|
思考题
- 静态链接时,链接器如何分配空间?
- 符号解析的过程是什么?
- 重定位的作用是什么?
- 重定位表的作用是什么?
- 如何处理符号冲突?
参考资料
- 《程序员的自我修养:链接、装载与库》
- GNU Binutils 文档
- System V ABI
- ELF 文件格式规范
- GCC 在线文档