Linking 链接
- 地址和空间的分配
- 符号解析(resolution), 符号绑定(binding)
- 重定位 (relocation)
重定位
由于每个模块都是单独compile出来的,比如:
main.c
调用了 foo.c
中的函数 foo()
, 在compiler编译main.c
的时候并不知道 foo
函数的地址,所以compiler暂时将目标地址搁置,等最后linking的时候,由linker去修正目标地址。地址修正的过程叫做重定位,每个需要被修正的地方叫做重定位入口(relocation entry)。
链接与符号
在链接中,将函数和变量统称为符号 (symbol)。
每个object file中都会有一个相应的符号表 (symbol table),这个表中记录了目标文件中用到的所有符号。
符号表中最重要的两类符号:
- 本目标文件中的全局符号,可以被其他目标文件引用
- 本目标文件中引用的全局符号,却没有定义在本目标文件中,这类是外部符号,如printf
#include <iostream>
using namespace std;
int g_var1 = 100;
int g_var2;
void foo(int i)
{
cout << "i = " << i << endl;
}
int main()
{
static int s_var1 = 99;
static int s_var2;
int a = 1;
int b;
foo(g_var1 +s_var1 + a);
}
针对上面这个C++代码,可以通过nm
命令查看其符号表。
C++ 符号修饰
符号修饰(decorate)也称为符号改编(mangle), compiler在将C++源码编译成object file时,会将函数和变量的名字进行修饰,形成符号名。
修饰方法:
- 所有的符号都以
_Z
开头 - 对于嵌套的名字(在namespace或者class里),后面紧跟
N
- 每个名字前面是名字长度
- 以
E
结尾 - 对于函数,参数表紧跟在
E
后面,对于int类型就是一个小写i
当查看符号表的时候想查看装饰前的名字的时候,推荐用如下命令:
extern "C": 将C++ code处理成C code
宏 __cplusplus
头文件声明了C语言的函数或变量,可以被C code或者C++ code引用。
如 C语言函数库string.h
中的
void* memset(void*, int, size_t);
- C code可以正确引用
memset
- C++ code会修饰
memset
, 这将导致linker无法与函数库string.h
中的memset
符号进行链接
C语言不支持extern "C"
语法,如果为了兼容C语言和C++定义两套头文件,未免过于繁琐。
解决问题的方法是利用C++的宏 __cplusplus
, C++编译器会在编译C++ code的时候默认定义这个宏,可以使用条件宏来判断当前编译单元是不是C++ code。
如果正在编译C++ code,则
memset
被extern "C"
包裹,否则,就是直接声明。上面这段代码中的技巧在所有的系统头文件里都被用到。
目标文件
- 可执行文件:
- Linux下位ELF (executable linkable format)
- Windows下PE (portable executable)
- 查看文件格式的命令:file
file filename
目标文件的构成
objdump: Display information form object file
可以用
size
命令查看ELF文件的代码段、数据段和bss段的长度