购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1.4 链接阶段

链接阶段的任务是生成一个程序或工程的所有目标文件,并将多个目标文件、库和数据文件进行汇合组成一个可执行的目标文件。执行链接阶段的程序称为链接器或链接编辑器,它通常独立于编译器,编译器通常实现前面所有的阶段。

链接阶段由称为链接器的程序ld自动执行,将Prog.o和其他必要的目标文件结合生成一个可执行的目标文件Prog。

大多数编译器(包括gcc)都会在编译过程结束时自动调用链接器,因此,要生成一个完整的二进制可执行文件,只需调用gcc而不需要任何特殊的选项。例如通过以下命令生成可执行文件:

我们也可以使用如下命令生成可执行文件:

链接器分为静态链接器和动态链接器。静态链接器只负责可执行文件或动态库文件的生成,静态链接器将所有文件的.text节链接在一起,程序所依赖的所有外部函数都存储在最终的可执行文件中。当运行可执行文件时,所有这些文件和外部函数都映射到内存中。而动态链接器是一段与可执行文件一起运行的代码,负责在程序运行时对外部函数和全局变量的引用进行解析。

链接阶段的主要工作有:

(1)符号解析:对目标文件中未定义的引用进行解析。下面举例说明:

①生成库的源代码。

Listing 2.1 生成库的源代码libadd.c

Listing 2.2 生成库的头文件libadd.h

Listing 2.3 生成库的源代码libanswer.c

Listing 2.4 生成库的头文件libanswer.h

②库测试代码。

Listing 2.5 测试库的应用源文件test.c

每个可重定位的目标文件,如libanswer.o,都有一个符号表,其中包含了模块libanswer.c中定义或引用的符号信息。从链接器的角度来看,有三种类型的符号信息:

●全局符号:在模块中定义的可被其他模块引用的符号,主要由非静态函数和非静态全局变量构成。例如,Listing 2.1的libadd.c中定义的全局变量gSummand、函数setSum-mand()和add()。

●外部符号:在其他模块中定义的可被当前模块引用的符号,主要由其他模块中定义的函数和变量构成。例如,Listing 2.3的libanswer.c中使用的外部函数setSummand()和外部变量gSummand。

●局部(静态)符号:只能被当前模块定义并引用的局部符号,主要由静态函数和静态全局变量构成。这些局部(静态)符号仅在当前模块内可见,不能被别的模块引用。例如,Listing 2.1的libadd.c中定义的静态全局变量sIntVar。

main()函数中定义的局部变量initValue在链接阶段是不可见的,只有当程序运行时才在栈上分配空间。

当由单一文件组成的程序生成目标文件时,汇编器会将所有针对符号(函数或者变量)的引用替换成相应的地址。

对于由多个文件组成的程序,即多模块程序,如果一个源文件中的代码引用了别的文件中定义的符号,汇编器在生成对应目标文件的过程中就会将这些符号标记为“unresolved”(未解析),并生成相应的重定位节信息。

多模块的工程在进行链接时需要将各个目标文件中的同类型节进行汇合组成单一的节。链接器将这些目标文件中标注为“unresolved”的信息是依据其他目标文件的信息确定的,引用重定位表修改符号,使得其指向正确的运行地址。给每个节和符号分配唯一的运行地址,给函数和全局变量分配一个唯一的运行地址,使得它们能正确地实现对函数或者变量的引用。例如,test.c中对函数setSummand()、add()、answer(),以及变量gSummand的引用需要用libadd.c和libanswer.c中的定义进行解析。

(2)重定位(Relocation):为代码和数据分配绝对地址并对引用进行更新。重定位是指对已分配了地址的标签(如函数或变量)进行重新更改地址的过程。当然,引用了重定位的标签的地方也得重新更新。一般来说下列两种情况需要重定位:

●在合并二进制代码中的节时需要重定位;

●在二进制代码中放置节时需要重定位。

在链接阶段,链接器将输入的多个目标文件以及系统代码和数据进行汇合,并为其中的每个符号分配运行时内存地址,即进行重定位操作,如图2.4所示。

图2.4 多个目标文件以及系统代码和数据的重定位

例如,在编译libanswer.c时并不知道gSummand变量的内存地址,链接器为变量gSum-mand在内存中分配空间,并在libanswer.o中使用gSummand时更新其地址。

重定位操作主要包含2个步骤:

(a)节和符号定义的重定位。在该步骤中,链接器首先将同类型的所有节合并到一起,然后为合并后的节分配运行时内存地址,也为模块中定义的每个符号分配运行时内存地址。此后,程序中的每条命令和全局变量都有唯一的一个运行时内存地址。

例如,在图2.4中,系统代码节与用户自定义的代码节合并成一个.text节。同样的道理,系统数据、.bss节与用户自定义的.data节合并成一个.data节和.bss节,合并时需要重定位。

(b)节中符号引用的重定位。这一步,链接器根据可重定位目标模块中的重定位项修改.text节和.data节中的每个符号引用,以便它们指向正确的运行时地址。比如图2.4中,当可执行代码test-shared-linked中对变量gSummand的引用以及对函数setSummand()、add()和answer()的调用都需要进行重定位。 Z/RPZMXGFUNW6td1MG6oNukVpNUZo1i0kVR0PjWx7bAg3RMO0Nv7GUC2Uawyo3Zu

点击中间区域
呼出菜单
上一章
目录
下一章
×