在链接阶段中,所有对应于源文件的.o文件、”-l”选项指定的库文件、无法识别的文件名(包括指定的.o目标文件和.a库文件)按命令行中的顺序传递给链接器。

下面看一下,链接的过程是怎样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gcc -v -o helloworld helloworld.o

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe
Target: mingw32
Configured with: ../src/gcc-6.3.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --with-gmp=/mingw --with-mpfr --with-mpc=/mingw --with-isl=/mingw --prefix=/mingw --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-pkgversion='MinGW.org GCC-6.3.0-1' --enable-static --enable-shared --enable-threads --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --enable-libgomp --disable-libvtv --enable-nls
Thread model: win32
gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)
COMPILER_PATH=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/;d:/software/mingw/bin/../libexec/gcc/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/bin/
LIBRARY_PATH=d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/;d:/software/mingw/bin/../lib/gcc/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/lib/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../
COLLECT_GCC_OPTIONS='-v' '-o' 'helloworld.exe' '-mtune=generic' '-march=i586'
d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/collect2.exe -plugin d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/liblto_plugin-0.dll -plugin-opt=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe -plugin-opt=-fresolution=C:\Users\MrBlu\AppData\Local\Temp\cceFgn0y.res -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -plugin-opt=-pass-through=-ladvapi32 -plugin-opt=-pass-through=-lshell32 -plugin-opt=-pass-through=-luser32 -plugin-opt=-pass-through=-lkernel32 -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -Bdynamic -o helloworld.exe d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../crt2.o d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/crtbegin.o -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0 -Ld:/software/mingw/bin/../lib/gcc -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/lib -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../.. helloworld.o -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/crtend.o
COLLECT_GCC_OPTIONS='-v' '-o' 'helloworld.exe' '-mtune=generic' '-march=i586'

crt2.o 、crtbegin.o、crtend.o是gcc加入的系统标准启动文件,对于一般应用程序,这些启动是必须的。
-Ldir:在库文件的搜索路径列表中添加dir目录。
-lname:添加链接库文件。

静态链接与动态链接

库有两种:静态库(.a、.lib)和动态库(.so、.dll)。
window上对应的是.lib、.dll。
linux上对应的是.a、.so

如果函数库的一份拷贝是可执行文件的物理组成部分,称之为静态链接。静态链接当链接程序时,需要使用的每个库函数的一份拷贝被加入到可执行文件中。静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直接运行,不过静态链接生成的程序体积较大(即使是在静态链接中,整个库文件也并没有全部装入到可执行文件中,所装入的只是需要的函数)。

如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库,称之为动态链接。动态链接允许系统提供一个庞大的函数库集合,可以提供许多有用的服务,程序在运行时寻找它们。动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态库才能运行。动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。

收集模块准备执行的三个阶段的规范名称是链接-编辑(link-editing)、载入(loading)和运行时链接(runtime linking)。静态链接的模块被链接编辑时载入,以便运行。动态链接的模块被链接编辑后载入,并在运行时进行链接以便运行。

程序执行时,在main函数被调用之前,运行时载入器把共享的数据对象载入到进程的地址空间。外部函数被真正调用之前,运行时载入器并不解析它们。所以动态链接即使链接了函数库,如果没有实际调用,也不会带来额外开销。

gcc编译器默认使用动态链接:

1
gcc -o helloworld_shared helloworld.o

gcc编译器使用静态链接:

1
gcc -static -o helloworld_static helloworld.o

在windows平台上使用mingw编译发现这两种方式生成的exe文件的大小都一样,为40kb,怀疑mingw的编译的库是静态库。
捕获1.JPG
下面看一下linux平台上使用gcc两种编译方式的文件大小区别:
Screenshot1png
可以看到helloworld_shared的大小为8344,而helloworld_static的大小达到844792。

-nostartfiles:
不链接系统标准启动文件,而标准库文件仍然正常使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gcc -v -nostartfiles -o helloworld helloworld.o

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe
Target: mingw32
Configured with: ../src/gcc-6.3.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --with-gmp=/mingw --with-mpfr --with-mpc=/mingw --with-isl=/mingw --prefix=/mingw --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-pkgversion='MinGW.org GCC-6.3.0-1' --enable-static --enable-shared --enable-threads --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --enable-libgomp --disable-libvtv --enable-nls
Thread model: win32
gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)
COMPILER_PATH=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/;d:/software/mingw/bin/../libexec/gcc/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/bin/
LIBRARY_PATH=d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/;d:/software/mingw/bin/../lib/gcc/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/lib/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../
COLLECT_GCC_OPTIONS='-v' '-nostartfiles' '-o' 'helloworld.exe' '-mtune=generic' '-march=i586'
d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/collect2.exe -plugin d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/liblto_plugin-0.dll -plugin-opt=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe -plugin-opt=-fresolution=C:\Users\MrBlu\AppData\Local\Temp\ccZxAfxD.res -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -plugin-opt=-pass-through=-ladvapi32 -plugin-opt=-pass-through=-lshell32 -plugin-opt=-pass-through=-luser32 -plugin-opt=-pass-through=-lkernel32 -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -Bdynamic -o helloworld.exe -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0 -Ld:/software/mingw/bin/../lib/gcc -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/lib -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../.. helloworld.o -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt
ertr000001.o:(.rdata+0x0): undefined reference to `_pei386_runtime_relocator'
collect2.exe: error: ld returned 1 exit status

-nostdlib
不链接系统标准启动文件和标准库文件,会提示因为没有链接系统标准启动文件和标准库文件,而链接失败。
该选项常用于裸机/bootloader、linux内核等程序,因为它们不需要启动文件、标准库文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gcc -v -nostdlib -o helloworld helloworld.o

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe
Target: mingw32
Configured with: ../src/gcc-6.3.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --with-gmp=/mingw --with-mpfr --with-mpc=/mingw --with-isl=/mingw --prefix=/mingw --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-pkgversion='MinGW.org GCC-6.3.0-1' --enable-static --enable-shared --enable-threads --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --enable-libgomp --disable-libvtv --enable-nls
Thread model: win32
gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)
COMPILER_PATH=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/;d:/software/mingw/bin/../libexec/gcc/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/bin/
LIBRARY_PATH=d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/;d:/software/mingw/bin/../lib/gcc/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/lib/;d:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../
COLLECT_GCC_OPTIONS='-v' '-nostdlib' '-o' 'helloworld.exe' '-mtune=generic' '-march=i586'
d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/collect2.exe -plugin d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/liblto_plugin-0.dll -plugin-opt=d:/software/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe -plugin-opt=-fresolution=C:\Users\MrBlu\AppData\Local\Temp\ccRAu2K4.res -Bdynamic -o helloworld.exe -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0 -Ld:/software/mingw/bin/../lib/gcc -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../../../mingw32/lib -Ld:/software/mingw/bin/../lib/gcc/mingw32/6.3.0/../../.. helloworld.o
helloworld.o:helloworld.c:(.text+0xa): undefined reference to `__main'
helloworld.o:helloworld.c:(.text+0x25): undefined reference to `puts'
helloworld.o:helloworld.c:(.text+0x3b): undefined reference to `printf'
helloworld.o:helloworld.c:(.text+0x47): undefined reference to `puts'
collect2.exe: error: ld returned 1 exit status

动态链接的优点

动态链接的优点是可执行文件的体积可以非常小。虽然运行速度稍慢一些,但动态链接能够更加有效的利用磁盘空间,而且链接-编辑阶段的时间也会缩短(因为链接器的有些工作被推迟到载入时)。

动态链接的主要目的是把程序与它们使用的特定的函数库版本中分离开来。取而代之的是,我们约定由系统向程序提供一个接口,该接口保持稳定,不随时间和操作系统的后续版本发生变化。

程序可以调用接口所承诺的服务,而不必担心这些功能是怎样提供的或者它们的底层实现是否改变。由于它是介于应用程序和函数库二进制可执行文件所提供的服务之间的接口,所以称它为应用程序二进制接口(Application Binary Interface,ABI)。

尽管单个可执行文件的启动速度稍受影响,但动态链接可以从两个方面提高性能:
1.动态链接可执行文件比功能相同的静态链接可执行文件的体积小。它能够节省磁盘空间和虚拟内存,因为函数库只有在需要时才被映射到进程中。
2.所有动态链接到某个特定函数库的可执行文件在运行时共享该函数库的一个单独拷贝。操作系统内核保证映射到内存中的函数库可以被所有使用它们的进程共享。这就提供了更好的I/O和交换空间利用率,节省了物理内存,从而提高了系统的整体性能。如果可执行文件是静态链接的,每个文件都将拥有一份函数库的拷贝,显然极为浪费。

动态链接使得函数库的版本升级更为容易。新的函数库可以随时发布,只要安装到系统中,旧的程序就能够自动获得新版本函数库的优点而无需重新链接。动态链接允许用户在运行时选择需要执行的函数库。这就使为了提高速度或提高内存使用效率或包含额外的调试信息而创建新版本的函数库是完全可能的,用户可以根据自己的喜好,在程序执行时用一个库文件取代另一个库文件。

动态链接是一种“just-in-time(JIT)”链接,这意味着程序在运行时必须能够找到它们所需要的函数库。链接器通过把库文件名或路径名植入可执行文件中来做到这一点。这意味着,函数库的路径不能随意移动。如果把程序链接到/usr/lib/libthread.so库,那么就不能把该函数库移动到其他目录,除非在链接器中进行特别说明。否则,当程序调用该函数库的函数时,就会在运行时导致失败。当在一台机器上编译完程序后,把它拿到另一台不同的机器上运行时,也可能出现这种情况。执行程序的机器必须具有该程序需要链接的函数库,而且这些函数库必须位于在链接器中所说明的目录。对于标准系统函数库而言,这并不成问题。

任何人都可以创建静态或动态的函数库。只需简单的编译一些不包含main函数的代码,并把编译所生的.o用正确的工具进行处理。
使用gcc程序编译的过程中的示例代码在Ubuntu下使用gcc创建静态和动态库:

首先将helloworld代码拆分开来:

分别为“helloworld.c”、“helloworld.h”、“main.c”,
helloworld.c内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

#define TRUE 1
#define FALSE 0

#define DEBUG_ENABLE

void helloworld(void){
int i = 0;
if(i == TRUE){
printf("hello\n");
}else{
#ifdef DEBUG_ENABLE
printf("i = %d\n",i);
#endif
printf("hello world\n");
}
}

helloworld.h内容如下:

1
2
3
4
#ifndef HELLO_WORLD_H 
#define HELLO_WORLD_H
void helloworld(void);
#endif

main.c内容如下:

1
2
3
4
5
6
7
#include  "helloworld.h"

int main()
{
helloworld();
return 0;
}

将helloworld.c生成动态链接库:

1
gcc -fPIC -shared helloworld.c -o libhelloworld.so

-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。 与位置无关的代码表示用这种方法产生的代码保证对于任何全局数据的访问都是通过额外的间接方法完成的。这使它很容易对数据进行重新定位,只要简单的修改全局偏移量表的其中一个值就可以了。类似的,每个函数调用的产生就像是通过过程链接表的某个间接地址所产生的一样。这样,文本可以很容易的重新定位到任何地方,只要修改一下偏移量表就可以了。所以当代码在运行时被映射进来时,运行时链接器可以直接把它们放在任何空闲的地方,而代码本身并不需要修改。

在缺省情况下,编译器并不产生与位置无关的代码,因为额外的指针解除引用操作将使程序在运行时稍慢。然而,如果不使用与位置无关的代码,所产生的代码就会被对应到固定的地址,这对于可执行文件来说确实很好,但对于共享库,速度却要慢一点,因为现在每个全局引用就不得不在运行时通过修改页面安排到固定的位置,这使得页面无法共享。

运行时链接器总能够安排对页面的引用。但是,使用位置无关代码,任务被极大的简化了。当然需要权衡一下,位置无关代码与由运行时链接器安排代码相比,速度是快了还是慢了。根据经验,对于函数库应该始终使用与位置无关代码。对于共享库,与位置无关的代码显得格外有用,因为每个使用共享库的进程一般都会把它映射到不同的虚拟地址(尽管共享库同一份物理拷贝)。

一个相关的术语是“纯代码(pure code)”。纯可执行文件是只包含代码(无静态或初始化过的数据)的文件。它之所以称为“纯”是因为它不必进行修改就能被其他特定的进程执行。它从堆栈或者其他(非纯)段引用数据。纯代码可以被共享。如果生成与位置无关代码(意味着共享),你通常也希望它是纯代码。

编译main时加入libhelloworld.so:

1
gcc helloworld.h main.c -L. -lhelloworld -o main

-L. : 标记告诉gcc函数库可能位于当前目录。
-l :后面加上动态链接库,动态链接库的名字前的lib不用加上去。传给C编译器的命令行参数里并没有提到函数库的完整路径名。它甚至没有提到在函数库目录中该文件的完整名字。实际上,编译器被告知根据选项-lname链接到相应的函数库,函数库的名字是libname.so——换句话说,“lib”部分和文件扩展名被省略掉了,但在前面加了一个“l”。

运行main,出错:

1
./main: error while loading shared libraries: libhelloworld.so: cannot open shared object file: No such file or directory

程序在运行时,会查找需要的动态库文件,若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。有多种方法可以解决:

a.将文件 libhelloworld.so复制到目录/usr/lib中,再执行则没有问题:

1
2
3
4
mrbluyee@mrbluyee:~/mypro/C$ sudo mv libhelloworld.so /usr/lib
mrbluyee@mrbluyee:~/mypro/C$ ./main
i = 0
hello world

b.既然连接器会搜寻LD_LIBRARY_PATH所指定的目录,那么我们可以将这个环境变量设置成当前目录:

1
2
3
4
mrbluyee@mrbluyee:~/mypro/C$ export LD_LIBRARY_PATH=$(pwd)
mrbluyee@mrbluyee:~/mypro/C$ ./main
i = 0
hello world

c.ldconfig命令

1
2
3
4
mrbluyee@mrbluyee:~/mypro/C$ sudo ldconfig ~/mypro/C
mrbluyee@mrbluyee:~/mypro/C$ ./main
i = 0
hello world

当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下”ldconfig 目录名”这个命令。此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,意即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库。上述指令让系统共享了~/mypro/C目录下的动态链接库。

可以查看程序执行时调用动态库的过程:

1
2
3
4
5
mrbluyee@mrbluyee:~/mypro/C$ ldd main
linux-vdso.so.1 (0x00007ffd56fa4000)
libhelloworld.so => /home/mrbluyee/mypro/C/libhelloworld.so (0x00007fcec730f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcec6f1e000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcec7713000)

将helloworld.c生成静态链接库:

a.先将helloworld.c编译生成.o文件:

1
gcc -c -o helloworld.o helloworld.c

b.通过ar工具将目标文件打包成.a静态库文件:

1
ar -rc libhelloworld.a helloworld.o

注意:静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。故ar工具里打包的一定是.o的文件,否则当运行连接了该静态库的可执行程序会报错。

编译main时加入libhelloworld.a:

1
gcc helloworld.h main.c -static -L. -lhelloworld -o main_static 

可以看到,使用动态链接库生成的mian与静态链接库生成的main_static的大小区别:
Screenshot2.png
动态链接库生成的mian大小为8288,而静态链接库生成的main_static的大小为844856。

删除libhelloworld.a后运行main_static:

1
2
3
4
mrbluyee@mrbluyee:~/mypro/C$ rm libhelloworld.a
mrbluyee@mrbluyee:~/mypro/C$ ./main_static
i = 0
hello world

程序照常运行,静态库中的helloworld函数已经链接到main_static文件中了。

多个文件生成动态/静态库的用法:

动态库:

1
2
1.gcc -fPIC -shared xxx1.c xxx2.c xxx3.c -o libxxx.so 
2.gcc -fPIC -shared xxx1.o xxx2.o xxx3.o -o libxxx.so

静态库:

1
ar -rc libxxx.a xxx1.o xxx2.o xxx3.o