Linux下gcc编译器生成和使用静态库和动态库学习笔记

我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被链接并拷贝到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被拷贝到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在。本质上说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。windows和linux库的二进制是不兼容的(主要是编译器、汇编器和连接器的不同)。本文主要通过举例来说明在Linux中如何创建静态库和动态库,以及使用它们。


一、基本概念

库的种类:

linux下的库有两种:静态库和共享库(动态库)。二者区别在于代码被载入的时刻不同。

静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。

共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。


库文件是如何产生的:

静态库的后缀是.a,它的产生分两步:

Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表

Step 2.ar命令将很多.o转换成.a,成为静态库

动态库的后缀是.so,它由gcc加特定参数编译产生。


库文件命名规范:

库文件一般放在/usr/local/lib,/usr/lib,/lib,或者其他自定义的lib下。

静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称

动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号


如何知道一个可执行程序依赖哪些库:

ldd命令可以查看一个可执行程序依赖的共享库,例如:

$ ldd /lib/i386-linux-gnu/libc.so.6

   /lib/ld-linux.so.2 (0xf7740000)

   linux-gate.so.1 =>  (0xf773f000)

可以看到libc命令依赖于linux-gate库和ld-linux库


可执行程序在执行的时候如何定位共享库文件:

当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)

对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目录找到库文件后将其载入内存

如:export LD_LIBRARY_PATH=’pwd’

将当前文件目录添加为共享目录


在新安装一个库之后如何让系统能够找到他:

如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:

1. 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径

2. 运行ldconfig,该命令会重建/etc/ld.so.cache文件



二、用gcc生成静态和动态链接库的示例

假设有1个类hello,和一个main函数。如下:

hello.h

[cpp]  view plain copy
  1. #ifndef HELLO_H   
  2. #define HELLO_H   
  3.     
  4. void hello(const char *name);   
  5.     
  6. #endif  

hello.c

[cpp]  view plain copy
  1. #include <stdio.h>   
  2. void hello(const char *name) {   
  3.   
  4.         printf("Hello %s!\n", name);   
  5. }  

main.c

[cpp]  view plain copy
  1. #include "hello.h"   
  2.  int main()   
  3.  {   
  4.      hello("world!");   
  5.      return 0;   
  6.  }  

hello.c是一个没有main函数的.c程序,因此不够成一个完整的程序,如果使用gcc –o 编译并连接它,gcc将报错,无法通过编译。前面提过,无论静态库,还是动态库,都是由.o文件创建的。那么我们如何才能让main.c调用hello类呢?也就是说该如何才能将hello.c通过gcc先编译成.o文件,并且让main.c在编译时能找到它?有三种途径可以实现:

1)通过编译多个源文件,直接将目标代码合成一个.o文件。

2)通过创建静态链接库libmyhello.a,使得main函数调用hello函数时可调用静态链接库。

3)通过创建动态链接库libmyhello.so,使得main函数调用hello函数时可调用动态链接库。


途径一:编译多个源文件


执行命令:

[html]  view plain copy
  1. $ gcc -c hello.c  
  2. $ gcc -c main.c  
这里提醒一下:gcc –o 是将.c源文件编译成为一个可执行的二进制代码。而gcc –c是使用GNU汇编器将源文件转化为目标代码。更多gcc编译选项的常识点这里。

这时可以看到生成了hello.o和main.o文件。

[html]  view plain copy
  1. $ ls  
  2. hello.c  hello.h  hello.o  main.c  main.o  

将两个文件链接成一个.o文件:

[html]  view plain copy
  1. $ gcc -o sayhello main.o hello.o  

查看此时已经生成了可执行文件sayhello

[html]  view plain copy
  1. $ ls  
  2. hello.c  hello.h  hello.o  main.c  main.o  sayhello  

运行

[html]  view plain copy
  1. $ ./sayhello  
  2. Hello world!!  


途径二:静态链接库


静态库文件名是以lib为前缀,紧接着是静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是libmyhello.a。创建静态库用ar命令。


删除途径一中生成的3个文件,回到原始的三个文件:

[html]  view plain copy
  1. $ rm hello.o main.o sayhello  
  2. $ ls  
  3. hello.c  hello.h  main.c  


开始尝试途径二,创建静态库文件libmyhello.a:

[html]  view plain copy
  1. $ gcc -c hello.c  
  2. $ ar rcs libmyhello.a hello.o  

查看一下已经生成了:

[html]  view plain copy
  1. $ ls  
  2. hello.c  hello.h  hello.o  libmyhello.a  main.c  

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。因此,我们在写需要连接的库时,只写静态库名就可以,如libmyhello.a的库,只写:-lmyhello

在main.c中,我们已包含了该静态库的头文件hello.h。现在在主程序main.c中直接调用它内部的函数:

[html]  view plain copy
  1. $ gcc -o sayhello main.c -static -L. -lmyhello       #这里-L.告诉gcc先在当前目录下查找库文件。  

查看一下,已经生成可执行文件sayhello

[html]  view plain copy
  1. $ ls  
  2. hello.c  hello.h  hello.o  libmyhello.a  main.c  sayhello  
  3.   
  4. <pre name="code" class="html">$ ./sayhello  
  5. Hello world!!  
 
 

前面提过静态库在编译过程中会被拷贝到目标程序中,运行时不再需要静态库的存在。这里可以简单验证一下:我们删除静态库文件,然后再试着调用函数hello看是否还能调用成功。

[html]  view plain copy
  1. $ rm libmyhello.a  
  2.   
  3. $ ./sayhello  
  4. Hello world!!  
程序照常运行,静态库中的函数已经被复制到目标程序中了,编译完成后,静态库就没用了,执行时不再需要静态库的存在。

静态链接库的一个缺点是,如果我们同时运行了许多程序,并且它们使用了同一个库函数,这样,在内存中会大量拷贝同一库函数。这样,就会浪费内存和存储空间。使用了共享链接库的Linux就可以避免这个问题。共享函数库和静态函数在同一个地方,只是后缀不同。比如,在Linux系统,标准的共享数序函数库是/usr/lib/libm.so。当一个程序使用共享函数库时,在连接阶段并不把函数代码连接进来,而只是链接函数的一个引用。当最终的函数导入内存开始真正执行时,函数引用被解析,共享函数库的代码才真正导入到内存中。这样,共享链接库的函数就可以被许多程序同时共享,并且只需存储一次就可以了。共享函数库的另一个优点是,它可以独立更新,与调用它的函数毫不影响。



途径三:动态链接库(共享函数库)


动态库文件名和静态库类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。


删除途径二中生成的2个文件,回到原始的三个文件:

[html]  view plain copy
  1. $ rm hello.o sayhello  
  2.   
  3. $ ls  
  4. hello.c  hello.h  main.c  

开始尝试途径三,创建静态库文件libmyhello.so:

按教程里,会报错:

[html]  view plain copy
  1. $ gcc -c hello.c  
  2.   
  3. $ ls  
  4. hello.c  hello.h  hello.o  main.c  
  5.   
  6. $ gcc -shared -fPIC -o libmyhello.so hello.o  
  7.   
  8. /usr/bin/ld: hello.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC  
  9. hello.o: could not read symbols: Bad value  
  10. collect2: ld returned 1 exit status  

正确方法是,这样就可以了:

[html]  view plain copy
  1. $ gcc -fPIC -shared -o libmyhello.so hello.c  

已生成libmyhello.so,是绿色。

[html]  view plain copy
  1. $ ls  
  2. hello.c  hello.h  libmyhello.so  main.c  

最主要的是GCC命令行的选项:

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

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


下面调用该动态链接库:

[html]  view plain copy
  1. $ gcc -o sayhello main.c -L. -lmyhello  
  2.   
  3. $ ls  
  4. hello.c  hello.h  libmyhello.so  main.c  sayhello  
  5.   
  6. <pre name="code" class="html">$ ./sayhello  
  7. Hello world!!  
 
 

成功!


按教程里说的:他以这种方式调用动态链接库出错,找不到动态库文件libmyhello.so:

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


程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。解决此类问题有如下三种方法:

(1)我们将文件 libmyhello.so复制到目录/usr/lib中。
(2)既然连接器会搜寻LD_LIBRARY_PATH所指定的目录,那么我们只要将当前目录添加到环境变量:export LD_LIBRARY_PATH=$(pwd)

(3)执行: ldconfig /usr/zhsoft/lib     

   

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



参考教程:

http://blog.csdn.net/jiayouxjh/article/details/7602729

http://blog.sina.com.cn/s/blog_54f82cc20101153x.html

http://navyaijm.blog.51cto.com/4647068/809424

你可能感兴趣的:(Linux下gcc编译器生成和使用静态库和动态库学习笔记)