Linux环境下编译链接库

没有任何一个重要的程序是用某种程序设计语言从零开始写出的。都是存在一组支撑库,这也就形成了进一步工作的基础。本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。由于windowslinux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。本文仅限于介绍linux下的库。

1.库的概念

根据代码被载入时期的不同,库又有静态库[*.a]和动态库[*.so]之分。静态库是在编译过程中链接阶段被链接的,所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行。动态库是在程序执行的时候被链接的。所以,即使程序编译完,库仍须保留在系统上,以供程序运行时调用。(TODO:链接动态库时链接阶段到底做了什么)。

1.1.静态库和动态库的比较

链接静态库其实从某种意义上来说也是一种复制粘贴,只不过它操作的对象是目标代码而不是源码而已。因为静态库被链接后库就直接嵌入可执行文件中了,这样就带来了两个问题。首先,就是系统空间被浪费了。这是显而易见的,想象一下,如果多个程序链接了同一个库,则每一个生成的可执行文件就都会有一个库的副本,必然会浪费系统空间。再者,人非圣贤,即使是精心调试的库,也难免会有错。一旦发现了库中有bug,挽救起来就比较麻烦了。必须一一把链接该库的程序找出来,然后重新编译。

动态库的出现正弥补了静态库的以上弊端。因为动态库是在程序运行时被链接的,所以磁盘上只须保留一份副本,因此节约了磁盘空间。如果发现了bug或要升级也很简单,只要用新的库把原来的替换掉就行了。

那么,是不是静态库就一无是处了呢?想象一下这样的情况:如果你用libpcap库编了一个程序,要给被人运行,而他的系统上没有装pcap库,该怎么解决呢?最简单的办法就是编译该程序时把所有要链接的库都链接它们的静态库,这样,就可以在别人的系统上直接运行该程序了。所谓有得必有失,正因为动态库在程序运行时被链接,故程序的运行速度和链接静态库的版本相比必然会打折扣。然而瑕不掩瑜,动态库的不足相对于它带来的好处在现今硬件下简直是微不足道的,所以链接程序在链接时一般是优先链接动态库的,除非用-static参数指定链接静态库。

1.2.库的命名

Linux环境下,动态库通常用.so为后缀,静态库用.a为后缀。例如:libhello.so libhello.a 为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀。例如: libxxxx.so.major.minorlibhello.so.1.0[1是主版本号,0是副版本号],由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。 ln -s libhello.so.1.0 libhello.so.1 ln -s libhello.so.1 libhello.so

2.编译库

Linux环境下动态库由gcc加特定的参数编译产生,静态库主要由以下两部产生:[1].由源文件编译生成一堆.o文件,每个.o文件里都包含这个编译单元的符号表。[2].运用ar命令将很多.o文件转换成.a文件。本节将详细介绍在Linux环境下如何生成库文件。

2.1.编译动态链接库

Linux系统下,编译动态链接库是一件非常简单地事情。只要在编译源程序时加上[-share]选项,将目标文件命名为*.so即可,这样所生成的执行程序即为动态链接库(在某种意义上动态链接库也是一种可执行文件)

下面通过一个例子来介绍如何生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.ctest_b.ctest_c.c,我们将这几个文件编译成一个动态库:libtest.so

/*so_test.h:*/
#include <stdio.h>
#include <stdlib.h>
void test_a();
void test_b();
void test_c();

/*test_a.c:*/
#include "so_test.h"
void test_a()
{
     printf("this is in test_a...\n");
}
/*test_b.c:*/
#include "so_test.h"
void test_b()
{
  printf("this is in test_b...\n");
}
 
/*test_c.c:*/
#include "so_test.h"
void test_c()
{
   printf("this is in test_c...\n");
}
代码段  1

将这几个文件编译成一个动态库:libtest.so 命令如下:

gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so

下面来解释一下上面使用到的gcc 参数:

先看一下这两个选项在manpage中的解释:

-shared   Produce a shared object which can then be linked with other objects to form an executable.  Not all systems support this option.  For predictable results, you must also specify the same set of options that were used to generate code (-fpic, -fPIC, or model suboptions) when you specify this option.

-fpic   Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine.  Such code accesses all constant addresses through a global offset table (GOT).  The dynamic loader resolves the GOT entries when the program starts (the dynamic loader is not part of GCC; it is part of the operating system).  If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000.  The 386 has no such limit.) Position-independent code requires special support, and therefore works only on certain machines.  For the 386, GCC supports PIC for System V but not for the Sun 386i.  Code generated for the IBM RS/6000 is always position-independent. When this flag is set, the macros "__pic__" and "__PIC__" are defined to 1.

-fPIC   If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table.  This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines. When this flag is set, the macros "__pic__" and "__PIC__" are defined to 2.

-shared  该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。

-fPIC :表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

2.2.编译静态链接库

此处我们仍用代码段1中的代码来做实例:

[1].将源文件编译成*.o文件,命令如下:

gcc -c test_a.c test_b.c test_c.c

通过ls命令我们可以看到工作目录下生成了test_a.o test_b.o test_c.o 三个文件。

下面来解释一下上面使用到的gcc参数:

看一下这两个选项在manpage中的解释:

-c  Compile or assemble the source files, but do not link.  The linking stage simply is not done.  The ultimate output is in the form of an object file for each source file. By default, the object file name for a source file is made by replacing the suffix .c, .i, .s, etc., with .o. Unrecognized input files, not requiring compilation or assembly, are ignored.

-c : 编译或汇编源文件,但是不链接。最终输出是根据源文件的类型来确定的。通常,目标文件名是将源文件名的后缀名.c, .i, .s 替换为.o产生的。无法识别的文件被忽略(不需要编译或汇编)。

[2].*.o文件创建成静态库,命令如下:

ar cr libtest.a test_a.o test_b.o test_c.o

通过ls命令我们可以看到工作目录下生成了libtest.a文件。

ar - create, modify, and extract from archives

ar命令可以用来创建、修改库,也可以从库中提出单个模块。

下面来解释一下上面使用到的ar参数:

看一下这两个选项在manpage中的解释:

c   Create the archive. The specified archive is always created if it did not exist, when you request an update.  But a warning is issued unless you specify in advance that you expect to create it, by using this modifier.

r   Insert the files member... into archive (with replacement). This operation differs from q in that any previously existing members are deleted if their names match those being added. If one of the files named in member... does not exist, ar displays an error message, and leaves undisturbed any existing members of the archive matching that name. By default, new members are added at the end of the file; but you may use one of the modifiers a, b, or i to request placement relative to some existing member. The modifier v used with this operation elicits a line of output for each file inserted, along with one of the letters a or r to indicate whether the file was appended (no old member deleted) or replaced.

c:创建一个 库。不管库是否存在,都将创建。

r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。

我们也可以从.o文件编译动态链接库,其命令为:

gcc test_a.o test_b.o test_c.o -shared -o libtest.so

此时生成.o文件时要加上-fPIC选项,如下:

gcc -c -fPIC test_a.c test_b.c test_c.c

3.使用库

2节我们详细介绍了如何创建库,生成库文件后我们如何正确使用这些库文件呢?在Linux环境下库的使用是比较简单地,只需要在使用到库中公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明库名,gcc将会从库中将公用函数连接到目标文件中。

下面以一个简单地程序来测试一下库的使用,测试程序代码为:

/*test.c*/
#include "so_test.h"
void main(void)
{
             test_a();
             test_b();
             test_c();
}

代码段 2

3.1.动态链接库的使用

我们删除相关源文件,只留动态库文件,测试程序源文件以及so_test.h文件,当前目录下文件如下:

moon@conmix:~/TS/lib$ ls

libtest.so  so_test.h  test.c

test.c文件以及库文件libtest.so链接生成可执行文件test,命令如下:

gcc test.c -L. -ltest -o test

注意:gcc会在库名前加上前缀lib,然后追加扩展名.a得到的库文件名来查找库文件。所以我们-l选项后面只跟test就行了。

下面,我们来运行一下这个可执行文件:

moon@conmix:~/TS/lib$ ./test

./test: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory

提示无法找到库文件,程序在运行时,通常会在/usr/lib/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。

我们有几种方法来解决上述问题:

[1]将我们库文件拷到默认搜素路径的目录下(/usr/lib/lib等目录)

[2]设置环境变量LD_LIBRARY_PATH,这个类似于 PATH 机制,比较直观,而且,可以放到 bashr中固化下来,也可以放到自己的 .bashrc 中只对本用户起作用;

[3] 如果启用了 ld.so.cache 的话,系统会在 /etc/ld.so.cache 中存储所有可引用的动态链接库。这个文件的内容可以通过 /etc/ld.so.conf 来指定;把库所在的路径加到文件末尾,并执行ldconfig刷新。这个是比较固定的机制,对全局所有用户都有影响;不过更改设置后需要 root 调用 ldconfig 来刷新一下。

我们这里先用第二种方法试一下:

moon@conmix:~/TS/lib$ export LD_LIBRARY_PATH=/home/moon/TS/lib

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

我们可以看到程序以正确链接到了我们的动态链接库。

下面我们在来用第三种方法试一下:

moon@conmix:~/TS/lib$ unset LD_LIBRARY_PATH

//进入su模式后,更改ld.so.conf文件

conmix@conmix:/home/moon/TS/lib$ cat /etc/ld.so.conf

include /etc/ld.so.conf.d/*.conf

/home/moon/TS/lib

conmix@conmix:/home/moon/TS/lib$ sudo ldconfig

conmix@conmix:/home/moon/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

我们看一下生成可执行文件的大小:

moon@conmix:~/TS/lib$ ll test

-rwxr-xr-x 1 moon conmix 8443 Oct 14 18:14 test*

我们看到test文件的大小为8443

3.2.静态链接库的使用

静态链接库的使用与动态链接库类似,我们删除相关源文件,只留静态库文件,测试程序源文件以及so_test.h文件,当前目录下文件如下:

moon@conmix:~/TS/lib$ ls

libtest.a  so_test.h  test.c

test.c文件以及库文件libtest.a链接生成可执行文件test,命令如下:

gcc test.c -L. -ltest -o test

下面我们也来运行一下这个可执行文件:

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

程序已经正确链接到了我们的静态链接库。

同样,我们看一下生成可执行文件的大小:

moon@conmix:~/TS/lib$ ll test

-rwxr-xr-x 1 moon conmix 8568 Oct 14 17:42 test*

我们看到test文件的大小为8568

在源码未做任何修改的情况下,采用静态链接库所生成的文件比采用动态链接库所生成的文件要大。

下面我们删除静态库文件,看程序是否可正常运行:

moon@conmix:~/TS/lib$ rm libtest.a

moon@conmix:~/TS/lib$ ls

so_test.h  test  test.c

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

4.更进一步

思考一下:

[1]如果同时存在动态链接库和静态链接库,gcc会怎么去链接呢?

我们来试验一下:

moon@conmix:~/TS/lib$ ls

libtest.a  libtest.so  so_test.h  test.c

moon@conmix:~/TS/lib$ gcc test.c -L. -ltest -o test

moon@conmix:~/TS/lib$ ./test

./test: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory

可见gcc会默认选择动态链接库,此时要选择静态链接库需在gcc上加-static选项。下面也来试验一下:

moon@conmix:~/TS/lib$ gcc test.c -L. -ltest -static -o test

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

下面来看一下gcc-static选项:

-static   The HP-UX implementation of setlocale in libc has a dependency on libdld.sl.  There isn't an archive version of libdld.sl.  Thus, when the -static option is specified, special link options are needed to resolve this dependency. On HP-UX 10 and later, the GCC driver adds the necessary options to link with libdld.sl when the -static option is specified.  This causes the resulting binary to be dynamic.  On the 64-bit port, the linkers generate dynamic binaries by default in any case.  The -nolibdld option can be used to prevent the GCC driver from adding these link options.

-static : 在支持动态链接的系统上,阻止链接共享库。改选项在其他系统上无效。

[2]如果各个库之间有函数名相同时会出现什么情况。

a、都采用动态链接

moon@conmix:~/TS/lib$

gcc test.c -L. -ltt -ltest -o test

moon@conmix:~/TS/lib$ ./test

this is in tt_a...

this is in test_b...

this is in test_c...

moon@conmix:~/TS/lib$

gcc test.c -L. -ltest -ltt -o test    

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

b、都采用静态链接

moon@conmix:~/TS/lib$

gcc test.c -L. -ltt -ltest -o test                              

moon@conmix:~/TS/lib$ ./test

this is in tt_a...

this is in test_b...

this is in test_c...

moon@conmix:~/TS/lib$

gcc test.c -L. -ltest -ltt -o test             

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

clibtt静态,libtest动态链接

moon@conmix:~/TS/lib$

gcc test.c -L. -ltt -ltest -o test                              

moon@conmix:~/TS/lib$ ./test

this is in tt_a...

this is in test_b...

this is in test_c...

moon@conmix:~/TS/lib$

gcc test.c -L. -ltest -ltt -o test             

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

this is in test_c...

由以上的结果可知,如果不同库之间有相同的函数名,生成的程序将会调用先被链接的库中的函数。

[3]如果模块间有函数名相同的函数会出现什么情况为了验证一下,我们将文件test_c.c中的函数名改为test_a

a、编译动态链接库

moon@conmix:~/TS/lib$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so

/tmp/cc8FgfhK.o: In function `test_a':

test_c.c:(.text+0x0): multiple definition of `test_a'

/tmp/ccmwwNLN.o:test_a.c:(.text+0x0): first defined here

collect2: ld returned 1 exit status

moon@conmix:~/TS/lib$

b、编译静态链接库:

moon@conmix:~/TS/lib$

ar cr libtest.a test_a.o test_b.o test_c.o

moon@conmix:~/TS/lib$

gcc test.c -L. -ltest -o test

moon@conmix:~/TS/lib$ ./test

this is in test_a...

this is in test_b...

moon@conmix:~/TS/lib$ rm libtest.a

moon@conmix:~/TS/lib$

ar cr libtest.a test_c.o test_b.o test_a.o

moon@conmix:~/TS/lib$ 

gcc test.c -L. -ltest -o test

moon@conmix:~/TS/lib$ ./test

this is in test_c...

this is in test_b...

由以上的结果可知,如果模块间有函数名相同时,在编译动态链接库的时候会报函数名重定义的错误。静态链接库可正常编译,生成的程序会调用在编译静态链接库时先被调用的模块中的函数。

另外,在编译动态库时现将源文件转换成.o文件,再由.o文件编译动态库时,也会出现相同的错误。思考一下gccld,以及ar命令吧,这个问题先留在这里,以后再去探讨

 [4]

 /* test0.c */
#include "so_test.h"
void main(void)
{
  test_a();
}
/* test1.c */
#include "so_test.h"
void main(void)
{
  test_a();
  test_a();
  test_a();
}
/* test2.c */
#include "so_test.h"
void main(void)
{
  test_a();
  test_b();
} 
/* test3.c */
#include "so_test.h"
void main(void)
{
  test_a();
  test_b();
  test_c();
}

       采用动态链接编译时上面4个源码产生的目标文件大小分别为:

       8365 8365 8404 8443

采用静态链接编译时上面4个源码产生的目标文件大小分别为:

       8440 8440 8504 8568

 

5.总结

本文详细探讨了库的概念,作用,编译以及使用。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值