当前位置:C++技术网 > 资讯 > 编译程序过程详解-编译器,汇编器,链接器(link.exe),加载器2

编译程序过程详解-编译器,汇编器,链接器(link.exe),加载器2

更新时间:2016-01-31 14:07:27浏览次数:1+次

下面让我们来分析一下链接器的工作过程:

我们跳过语法分析,直接来到目标文件的生成,假设我们有一个A.cpp文件,如下定义:

    int n = 1;

    void FunA()

    {

        ++n;

    }

 

    它编译出来的目标文件A.obj就会有一个区域(或者说是段),包含以上的数据和函数,其中就有n、FunA,以文件偏移量形式给出可能就是下面这种情况:

    偏移量    内容    长度

    0x0000    n       4

    0x0004    FunA    ??

    注意:这只是说明,与实际目标文件的布局可能不一样,??表示长度未知,目标文件的各个数据可能不是连续的,也不一定是从0x0000开始。

    FunA函数的内容可能如下:

    0x0004 inc DWORD PTR[0x0000]

    0x00?? ret

    这时++n已经被翻译成inc DWORD PTR[0x0000],也就是说把本单元0x0000位置的一个DWORD(4字节)加1。

 

    有另外一个B.cpp文件,定义如下:

    extern int n;

    void FunB()

    {

        ++n;

    }

    它对应的B.obj的二进制应该是:

    偏移量    内容    长度

    0x0000    FunB    ??

    这里为什么没有n的空间呢,因为n被声明为extern,这个extern关键字就是告诉编译器n已经在别的编译单元里定义了,在这个单元里就不要定义了。由于编译单元之间是互不相关的,所以编译器就不知道n究竟在哪里,所以在函数FunB就没有办法生成n的地址,那么函数FunB中就是这样的:

    0x0000 inc DWORD PTR[????]

    0x00?? ret

    那怎么办呢?这个工作就只能由链接器来完成了。

    为了能让链接器知道哪些地方的地址没有填好(也就是还????),那么目标文件中就要有一个表来告诉链接器,这个表就是“未解决符号表”,也就是unresolved symbol table。同样,提供n的目标文件也要提供一个“导出符号表”也就是exprot symbol table,来告诉链接器自己可以提供哪些地址。

 

    好,到这里我们就已经知道,一个目标文件不仅要提供数据和二进制代码外,还至少要提供两个表:未解决符号表和导出符号表,来告诉链接器自己需要什么和自己能提供些什么。那么这两个表是怎么建立对应关系的呢?这里就有一个新的概念:符号。在C/C++中,每一个变量及函数都会有自己的符号,如变量n的符号就是n,函数的符号会更加复杂,假设FunA的符号就是_FunA(根据编译器不同而不同)。

    所以,

    A.obj的导出符号表为

    符号    地址

    n       0x0000

    _FunA   0x0004

    未解决符号为空(因为他没有引用别的编译单元里的东西)。

    B.obj的导出符号表为

    符号    地址

    _FunB   0x0000

    未解决符号表为

    符号    地址

    n       0x0001

    这个表告诉链接器,在本编译单元0x0001位置有一个地址,该地址不明,但符号是n。

    在链接的时候,链接在B.obj中发现了未解决符号,就会在所有的编译单元中的导出符号表去查找与这个未解决符号相匹配的符号名,如果找到,就把这个符号的地址填到B.obj的未解决符号的地址处。如果没有找到,就会报链接错误。在此例中,在A.obj中会找到符号n,就会把n的地址填到B.obj的0x0001处。

 

    但是,这里还会有一个问题,如果是这样的话,B.obj的函数FunB的内容就会变成inc DWORD PTR[0x000](因为n在A.obj中的地址是0x0000),由于每个编译单元的地址都是从0x0000开始,那么最终多个目标文件链接时就会导致地址重复。所以链接器在链接时就会对每个目标文件的地址进行调整。在这个例子中,假如B.obj的0x0000被定位到可执行文件的0x00001000上,而A.obj的0x0000被定位到可执行文件的0x00002000上,那么实现上对链接器来说,A.obj的导出符号地地址都会加上0x00002000,B.obj所有的符号地址也会加上0x00001000。这样就可以保证地址不会重复。

 

    既然n的地址会加上0x00002000,那么FunA中的inc DWORD PTR[0x0000]就是错误的,所以目标文件还要提供一个表,叫地址重定向表,address redirect table。

 

    总结一下:

    目标文件至少要提供三个表:未解决符号表,导出符号表和地址重定向表。

    未解决符号表:列出了本单元里有引用但是不在本单元定义的符号及其出现的地址。

    导出符号表:提供了本编译单元具有定义,并且可以提供给其他编译单元使用的符号及其在本单元中的地址。

    地址重定向表:提供了本编译单元所有对自身地址的引用记录。
现在我们可以来看看几个经典的链接错误了:
    unresolved external link..
    这个很显然,是链接器发现一个未解决符号,但是在导出符号表里没有找到对应的項。
    解决方案么,当然就是在某个编译单元里提供这个符号的定义就行了。(注意,这个符号可以是一个变量,也可以是一个函数),也可以看看是不是有什么该链接的文件没有链接
    duplicated external simbols...
    这个则是导出符号表里出现了重复项,因此链接器无法确定应该使用哪一个。这可能是使用了重复的名称,也可能有别的原因。

link.exe是Windows平台的链接器,它将cl.exe编译生成的obj文件,资源编译器生成的.res文件,以及lib目录下的lib文件等链接成可执行的exe文件、dll文件等。这个你要记住。链接器不仅仅执行cl.exe编译生成的obj文件哦。link.exe的输入文件包括obj文件、lib文件、exp文件、def文件、res文件、txt文件、ilk文件。输出文件是exe文件、dll文件、sys文件等可执行程序文件。

有些资料是参考的,也不算是原创。