[上一篇 linux g++ 链接器] (http://windmissing.github.io/compile/2016-07/linux-g++-linking.html)
由编译源代码生成了包含机器指令的文件,我们称之为目标文件。
源代码中的变量或者函数,我们称之为符号。
通常情况下,我们不会把所有工作都写在一个源代码文件上,而是分成多个文件。
既然分成多个文件,就会这样的情况:一个文件里要使用的某个符号实际上是在另一个文件里定义的。这种关系,我们称之为引用。
那么链接器要做的事情,就是处理好这些引用关系,使得当一个目标文件想要引用另一个目标文件中的符号时,它能顺利地找到并使用。
当所有符号都能正确引用时,程序就可以正确地执行了。
因此,关于链接器的概念,记住三个关键字:目标文件、符号、引用。
举个例子:
- 文件一:main.cpp
#include <iostream>
using namespace std;
void myfun();
int main()
{
myfun();
return 0;
}
- 文件二:a.cpp
void myfun()
{
cout<<"myfun in a.cpp"<<endl;
}
在这个例子中,main.cpp和a.cpp分别编译成目标文件main.o和a.o。
myfun
和main
都是符号。
main.cpp中调用了myfun(),这里的myfun()与a.cpp中myfun()的关系就是引用关系。
目标文件
上文的例子上,main.o和a.o都是目标文件,但目标文件不只有这一种。
可执行文件其实也算是一种目标文件。
但链接器所接受的目标文件有三种:可重定位目标文件(.o, .a),共享目标文件(.so)。其中.a文件可以看作是若干个.o文件的打包。
.o | .a | .so |
---|---|---|
可重定位目标文件 | 多个可重定位目标文件 | 共享目标文件 |
一本书 | 一叠书 | 一本厚书 |
普通目标文件 | 静态库 | 动态库 |
不管是可重定位目标文件、共享目标文件还是可执行文件,所有的目标文件都有着相似的格式 --- ELF。
表中列出了主要内容。但目前,我们只需要知道ELF主要分为两部分,分别是指令部分(红色)和数据部分(绿色)。
符号
符号是指源代码中的变量和函数。
符号也是分多种的。链接器并不是对所有符号都关心。它只关心其中几种。让我们一一分析:
extern int buf[];
static int *bufp0 = &buf[0];
int *bufp1;
void swap()
{
int temp;
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
这段代码中有五个符号:buf, bufp0, bufp1, swap, temp
根据符号的作用域,可以把符号分为这四类:
局部符号
例如temp,是在函数内定义的非static符号。这类符号是在栈里管理的,因此链接器对局部符号一点都不关心。
静态符号
例如bufp0,是有static关键籽修饰的符号。它在本文件内定义,也只在本文件内使用。
链接器对静态符号的处理比较简单,只要保证模块中一个符号只有一个定义就好了。
外部符号
例如buf,它是由其它模块定义,但被本模块引用了的符号。有extern修饰
其它模块的可引出符号,如果被本模块使用了,就是本模块的外部符号
编译器发现模块中的符号没有定义时,就会从别的模块中找到它的定义,并产生正确的引用。
可引出符号/全局符号
例如bufp1和swap,是由本模块定义,且能被本模块和其它模块引用的符号。
非Static全局函数,非static全局变量都是可引出符号。
链接器对可引出符号的处理比较复杂。它需要统一管理这些符号:(1)处理重定义问题(2)当某个模块需要使用这个符号时能够搜索到(3)向使用者提供符号的地址,以产生正确的引用。
总结:
符号类型 | 符号举例 | owner | 作用域 | 链接器的处理方法 |
---|---|---|---|---|
局部符号 | temp | 函数内定义 | 函数内使用 | 不关心 |
静态符号 | bufp0 | 文件内定义 | 文件内使用 | 处理文件内重定义 |
外部符号 | buf | 其它目标文件内定义 | 本目标文件内使用 | (1)找到符号在其它文件的定义 (2)确定符号地址 (3)产生正确引用 |
可引出符号/全局符号 | bufp1和swap | 本目标文件内定义 | 所有参与链接的目标文件都可以使用 | (1)解决重定义问题 (2)当某个模块需要使用这个符号时能够搜索到 向使用者提供符号的地址 |
引用
链接器主要需要处理的是本文件内的外部符号到其它文件的全局符号之间的引用。
不管输入的目标文件都是.o,.a还是.so,互相之间的引用都能正常进行,才能产生可执行文件。
本文接下来的篇幅都用来介绍不同目标文件之间是如何产生正确的引用的。