本节使用的源代码有:Listing 2.1、Listing 2.2、Listing 2.3、Listing 2.4和Listing 2.5。
(1)为每个源文件独立生成目标文件。命令如下:
(2)对多个目标文件进行联合,生成静态库libtest.a。命令如下:
Linux系统中ELF的动态链接文件称为动态共享对象(Dynamic Shared Ob jects),也就是共享库,一般以.so扩展名结尾,如libc.so。Windows系统则以.dll结尾。
(1)为每个源文件独立生成目标文件。在生成共享库中的目标文件时,需要将其编译为位置独立的模块,即编译时需要增加选项-fPIC。命令如下:
(2)生成动态库libtest.so。使用-shared选项生成一个共享库时,该共享库可以与其他对象链接以形成可执行文件。为保持一致性,在指定此链接器选项时,目标文件在编译时也必须指定同一组选项(-fpic、-fPIC或模型子选项)。注意:并非所有系统都支持此选项。命令如下:
.so文件和.a文件的区别是:.so文件在运行时被动态加载,可执行文件和运行内存映像只包含它们实际使用的函数。.a文件在编译时被静态加载。
在Linux中使用gcc生成的可执行文件,实际上是由gcc使用collect2将用户代码和相应的库进行自动链接生成的。在生成可执行文件时,与已有库的链接分为两类:静态链接和动态链接。
(1)静态链接。静态链接生成可执行文件的方法为:
其中,-static选项用于强制使用静态库。因为gcc在链接时优先选择动态库,只有当动态库不存在时才使用静态库。加上-static选项可强制使用静态库。上述命令在生成可执行文件test-statically-linked时使用的是静态库libtest.a。
静态链接将应用程序中使用到的库中的例程全部复制到可执行文件中,静态链接示意图如图2.5所示,因此,需要占用更大的内存空间,但程序在运行时系统不需要库。静态链接的优点是速度快、可移植性好,出错的可能性较低。
在编译时,静态链接器将收集所有相关的目标文件,如test.o、libtest.a和libc.a(静态库,一个目标文件包),根据应用目标文件中的重定位信息将文件组合成一个二进制文件。因此,当链接多个目标文件时,生成的二进制文件可能会非常大。例如:
图2.5 静态链接示意图
(2)动态链接。动态链接是指在程序装载运行时通过动态链接器将程序所需的所有共享库(.so等)装载到进程空间,当程序运行时才将动态库链接在一起形成一个完整的进程。由于多个程序可以共享一个库,这样不仅可以节约内存和磁盘空间,还具有更高的扩展性。
与静态链接不同,动态链接需要共享库来创建动态链接的可执行文件,由系统在装载时完成。动态链接示意图如图2.6所示。动态链接由链接装载器完成,在程序装载时或更晚的时候复制所需的代码。通过动态链接生成的输出文件将包含可执行文件的代码和所依赖的共享库(依赖库)的名称,该名称嵌入在二进制文件中。当二进制文件被执行时,动态链接器将找到需要加载的依赖库并将它们链接在一起,因此,将链接阶段从编译时推迟到了运行时。
图2.6 动态链接示意图
使用动态库libtest.so生成可执行文件use-shared-linked的命令为:
共享库的动态链接以及可执行文件的装载运行如图2.7所示。当装载并运行test-shared-linked时,首先将部分链接的test-shared-linked装载到内存。test-shared-linked包含一个.interp节,该节中含有动态链接器的路径名“/lib/ld-linux.so.2”,动态链接器本身是一个共享对象。
使用以下命令可查看二进制文件的段信息:
图2.7 共享库的动态链接以及可执行文件的装载运行
使用以下命令可查看二进制文件的重定位信息:
动态链接器可通过执行以下重定位来完成链接任务:
●将libc.so的代码和数据重新定位到进程空间的内存段中。
●将libtest.so的代码和数据重新定位到另一个内存段中。
●对test-shared-linked引用libc.so和libtest.so中定义的符号进行重新定位。
●动态链接器将控制权传递给应用程序。
自此,共享库的位置在程序运行期间就固定不变了。
静态链接和动态链接各有利弊:
●静态链接允许在一个二进制文件中包含所有的依赖库,使其更易于移植和执行,但二进制文件较大。
●动态链接允许二进制文件更小,但代价是必须确保二进制文件的依赖库存在二进制文件运行时的目标系统中。