1. 可执行目标文件
1.1 如何得到可执行目标文件
链接程序collect2/ld,将所有的可重定位目标文件静态链接在一起,就得到了可执行目标文件。
静态链接动态库时,只是留下函数的接口,当程序运行时再动态加载动态库。
简单理解的话,链接的过程,其实就是将所有的.o文件合并为一个可执行文件的过程。
链接时,链接程序(静态链接器)做了什么事情
链接器主要做两件事情,
第一:符号解析
第二:地址重定位。
有关这两件事情,我们在前面大概的介绍过,接下来我们将会较为详细的介绍下这两件事。
1.2 符号解析
1.2.1 符号解析的目的
确定模块中引用的每个符号都有明确的定义,并将每个符号的引用与定义关联起来。
如果你引用了一个符号,结果这符号没有被定义,程序是无法运行的。
比如:
(1)如果引用的全局变量没有定义的话
符号没有定义,这个变量就没有对应的空间,没有变量空间,就没办法进行读写了。
(2)如果引用的函数没有定义的话
函数体(函数指令)就不存在,函数没办法调用。
1.2.2 如何解析符号
检查模块(.o)的.symtab符号表,看符号的定义情况。
(1)情况1:符号就是在本模块定义的(本地符号)
像这种情况的话,链接器不需要做什么太多的解析工作,因为符号就是在本模块中定义的,每个符号对应的空间就被定义在了本模块的某个节中(比如.text、.data等节),引用该符号时肯定没有问题。
(2)情况2:符号由本模块引用,但是在其它模块定义的(全局符号)
被标记为UND的符号就是这种情况,UND表示此符号只是在本模块引用,但是在其它模块定义的。
其实链接器进行符号解析时,重点解析的是标记为UND的符号,解析时会检查UND符号是否在其它模块中有定义。
怎么解析UND符号的?
解析时,对于本模块中UND的符号,链接器会查看其它模块的.symtab符号表,看该符号是否在其它模块中有定义,如果找到定义,就将符号的引用和定义关联起来,如果找不到,链接器就会报undefined reference to ‘符号名’的错误。
比如:
a.c
int fun(); int main(void) { fun(); return 0; }
gcc编译链接
zxf@ubuntu:~/Desktop$ gcc a.c b.c -o a /tmp/ccTyxboK.o: In function `main': a.c:(.text+0xa): undefined reference to `fun' collect2: error: ld returned 1 exit status
undefined reference to `fun'这个错误是由链接器报的。
(3)说说有关编译链接时的报错
其实编译链接时,四个阶段都有可能会报错误。
1)预处理
a.c ---> a.i(cpp、cc1)
报预处理的错误,凡是宏、头文件包含、预编译等错误都是在预处理阶段,由“预处理器”报的。
2)编译阶段
a.i ----> a.s (cc1)
这个阶段报的都是c语法错误,因为这个阶段的主要目的就是按照c语法格式去解释c源码文件,然后翻译得到汇编,所以编译阶段报的主要是c语法的错误,只要你不按照c语法格式的要求写,就会报C语法错误。
比如:
(a)各种c关键字用错、写错
· 写错:return 10 写成了 retrun 10
· 用错:
switch(a) { case 12.6: a = 10;//c语法要求,switch的case不能跟浮点数 }
(b)各种重复定义
编译时,是以单个源码文件为单位来操作的,单个源码文件(模块)中的一些关键内容,是不能重复定义的。
· 变量重复定义的错误
a.c
int a = 100; int a = 101; int main(void) { }
疑问:int a;
int a = 100;
算不算重复定义。
答:这种不算,为什么不算,后面解释。
同一模块的重复定义,编译器编译时就会检查出来,不同模块之间的重复定义,只能留给链接器检查。
a.o
int a = 10;
b.o
int a = 100;
· 类型重复定义的错误
比如:
a.c
struct student { int num; }; struct student { int num; }; int main(void) { }
旁注:像int、float、char、struct student等类型,是c语法格式才有的东西,是专门给cc1编译器用的,这些类型决定了数据空间的存储结构和大小,比如:
int a; //整形存储结构,4字节 float b;//浮点存储结构,4字节
一旦编译器cc1将其编译为汇编后,这些类型就消失了。
其实所有属于C语法格式的东西(比如,c的类型、关键字等),在cc1编译之后,都将不复存在。
· 模块中函数重定义的错误
a.c
int fun() { ... } int fun() { ... }
(c)汇编
a.s ——————> a.o (as)
事实上,汇编阶段并不会报什么错误,不报错的原因,并不是因为汇编器as没有检错的能力,而是只要c源码被编译为a.s时通过了,也就是说只要c源码没有错误,那么编译得到的a.s就没问题,从a.s汇编为a.o时,其实没有什么需要报的错误。
(d)链接
报各种链接时产生的链接错误,比如
· 某模块引用的符号,找遍其它所有模块,都找不到它的定义时,就会报undefined reference to ‘符号名’的错误
· 不同模块有强符号重复定义时,报重复定义的错误
- 初始化了全局变量的重复定义
a.o
int a;
b.o
int a = 100;
链接时会报a重复定义了。
如果不想报错怎么?
+ 给其中一个加static,或者两个都加static
加了static后,符号就是本地的,对其它模块无影响。
+ 将其中一个的初始化去掉
没有初始化的符号是弱符号,强弱符号不会冲突,有关强弱符号的问题,后面还会介绍。
- 同名函数的重复定义
a.o
float fun(int a) { }
b.o
int fun(void) { }
旁注:有关c++/java中同名函数的问题。
· c的情况
对于c来说,不管函数参数一不一样,只要函数名相同,就被认为是重名。
· c++/java等
这类语言的函数允许重载,重载的意思就是说,函数名相同不一定重名,只要参数不同,就是两个不同的函数。
int fun(int a) { } int fun(int b, int c) { }
因为在进行函数重名的判断时,函数名和函数参数会合在一起判断,只有当函数名和参数完全相同,才能算是重名。