静态库和动态库系列(2)
这个篇幅主要学习一下如何在linux下去生成.so和.a 学习来源http://blog.chinaunix.net/uid-23592843-id-223539.html
先说明一点: 尽管在windows下和在linux下都有静态库和动态库的概念,但是由于编译器,汇编器,连接器的不同.windows的动态(lib)静态(dll)库和linux下的动态(.so)静态(.a)库是不兼容的.
库的产生步骤:
静态库 .a
step1:由源文件(.h .cpp) 编译生成一堆.o, 每个.o都包含这个.o对应的编译单元的符号表
step2:ar命令将很多.o转换成.a,成为静态库
动态库 .so
由 gcc加特定参数编译产生
库的命名:
在linux下,库一般放在/usr/lib/ 和/lib下.可能是自己的库生成的位置一般放在/usr/lib.系统的一般放在/lib.有待查验
静态库: 名字一般为 libxxxx.a --->xxxx就是该lib的名字
动态库: 名字一般为 libxxxx.so.major.minor--->xxxx是该lib的名字,major是主版本好,minor是副版本号
如何知道一个可执行程序依赖哪些库
ldd 命令可以查看一个可执行程序依赖的共享库
可执行程序在执行的时候如何定位动态库(共享库)文件
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径.为了知道这个绝对路径就需要系统动态载入器.
对于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文件
用hello.h hello.c main.c 的实际创建过程来讲解如何在linux下创建动态库和静态库
1 //文件名 hello.h 2 3 #ifndef HELLO_H_ 4 #define HELLO_H_ 5 6 void hello(const char* name); 7 8 #endif
|
//文件名 hello.c #include <stdio.h> void hello(const char* name) { printf("hello %s\n", name); }
|
1 //文件名main.c 2 3 #include "hello.h" 4 5 int main() 6 { 7 hello("Lucy"); 8 return 0; 9 }
|
如果我们要生成hello.o的话 用 gcc -c hello.o hello.h hello.c 可以成功,但是因为hello.c之中没有main函数,所以并不是一个完整的程序.因此用gcc -o hello hello.o 来编译并连接它的时候GCC会报错
注意: 无论是静态库还是动态库,都是由.o文件来创建的,因此我们必须先将.c编译成.o文件
这个时候我们有三种思路
1.编译多个源文件,直接将目标代码合成一个.o文件
2.创建静态链接库libmyhello.a, 使得main函数调用hello函数时可调用静态链接库
3.创建动态链接库libmyhello.so,使得main函数调用hello函数时刻调用动态链接库
思路一具体过程
#gcc -c hello.c ---->会产生出hello.o
#gcc -c main.c ---->会产生出main.o
#gcc -o hello hello.o main.o ---->会产生可执行文件hello
./hello -------->hello lucy
为什么不使用 gcc -o hello hello.o ?是因为上面说的hello.c里面没有main函数,gcc编译会报错的.
这里来详细说明一下gcc -o 和 gcc -c的具体过程: gcc -o 是将.c源文件编译成为一个可执行的二进制代码,这个过程包括调用作为GCC内的一部分真正的C编译器(ccl),以及 调用GNU C编译器的输出中实际可执行代码的外部GNU汇编器(as) 和连接器工具(ld) gcc -c 是使用GNU汇编器将源文件转换为目标代码之后就结束,在这个情况下,只调用了c编译器(ccl)和汇编器(as),而连接器(ld)没被执行,所以输出的目标文件*.o中不会包含作为linux程序在被装载和执行时所必须的包含信息,但它可以在以后被连接到一个程序 |
思路二:静态链接库
静态库的命名规则libxxx.a-->xxx是库的名字 .例如我们将创建名字叫hello的静态库 那么静态库文件名libhello.a. 这一点很容易搞错的
创建静态库用ar命令
#gcc -c hello.c
# ar rsc libhello.a hello.o
这样静态库就创建成功了
静态库制作完成后,如何使用静态库的内部的函数呢.? 只需要在使用到这些内部函数的源程序中包含这些内部函数的原型声明,然后用gcc -o 命令生成目标文件时候指明静态库名就可以了.gcc将会从静态库中将公用函数连接到目标文件中.注意:gcc会在静态库名前自动加上lib,然后追加.a后缀,因此我们在写需要连接的库时,只写名字就可以了 例如如果要连接libhello.a ,那么gcc语句只要写 -lhello |
#gcc -o hello main.c -static -L. -lhello --->用main.c和静态库生成可执行文件hello
#./hello -------->hello lucy
在生成了可执行hello后即使我们删除掉libhello.a也没关系,hello也可以执行.因为hello函数以及被生成到可执行文件中了.
静态库的一个缺点是,如果我们同时运行了许多程序,并且这些程序都使用了同一个库函数,那么在内存中这个库函数将会被拷贝很多份,这样会浪费很多内存空间.使用共享链接库(动态库)可以避免这个问题. 静态库和动态库是在一个地方的,只不过后缀有所不同.在典型的linux系统下,动态库的路径是/usr/lib/libxxx.so 当一个程序在使用动态库时候,在连接阶段并不会把函数代码连接进来,而只是链接函数的一个引用.当需要调用这个函数的时候,函数引用会被解析,此时动态库的代码才真正导入到内存中.这样,动态库的函数可以被许多程序同时共享,并且只需要在内存中存储一次就可以. 动态库的另一个优点是他可以独立更新,与调用他的函数毫不影响. |
思路三:动态链接库(共享函数库)
动态库的命名libxxx.so 例如动态库名字hello 则动态库的文件名就是libhello.so
#gcc -c hello.c
# gcc -shared -fPIC -o libhello.so hello.o
这样就生成了动态链接库libhello.so
"PIC"命令行标记告诉GCC产生的代码不要包含对函数和变量具体内存位置的引用,这是因为现在还无法知道使用该动态库代码的应用程序将会把动态库连接到哪一段内存地址空间.这样编译出的hello.o 可以被用于建立共享链接库.建立共享链接库只需要使用GCC的-shared标记即可 |
#gcc -o hello main.c -L. -lhello
在程序中使用动态库和使用静态库是完全一下的,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后再用gcc命令生成目标文件时指明动态库名进行编译. -L. 是告诉GCC,动态库可能在当前目录.否则GNU连接器会查找标准系统函数目录:搜索elf文件的DT_RPATH段, 搜索环境变量LD_LIBRARY_PATH,搜索/etc/ld.so.cache文件类别, 搜索/lib ./usr/lib目录 如果找到了库文件就载入到内存.但是由于我们把动态库生成到了当前文件夹下,那么一直到搜索完/lib和./usr/lib目录都没有找到库,因此会报错.加上-L.就会连当前文件夹也搜索.就会找到了. 或者我们可以在LD_LIBRARY_PATH中加入当前文件夹,这样也会找到 #export LD_LIBRARY_PATH=$(pwd) 如果我们想要让这个动态链接库被系统共享,可以执行"#ldconfig 目录名" 这个命令.此命令的功能是在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库 |
#export LD_LIBRARY_PATH=${pwd} / #ldconfig /usr/Lihong/lib (必须是root权限)
# ./hello {如果没找到库会报的错误:: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory}
这里再说明一些小的细节: 当在一个目录下面,你既生成了静态库又生成了动态库,而且静态库和动态库的名字一样(例如libhello.a libhello.so),那么在程序执行的时候回优先调用动态库.
如果要查看可执行文件调用库的过程和调用哪些库的话用ldd命令(动态库和静态库都是调用这个命令)
可以看看可执行文件hello.先调用了动态库 linux-gate.so 再调用了libmyhello.so 再调用了/lib/ld-linux.so |
接下来,基本就把linux下面的创建动态库和静态库就学习完成了.但是我们现在仅仅是学会了怎么用.我们用下面的一个小虚框来对上面一些知识点进行补充学习
上面学习到的编译参数说明 -shared 该选项指定生成动态库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用这个选项的话,生成的可执行程序无法连接动态库 -fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的,所以动态载入时是通过代码拷贝的方式来满足不同进程的需求,而不能达到真正代码 共享的目的 -L. 表示要连接的库在当前目录中 -ltest 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前加上lib,后面加上.so来确定库的名称 LD_LIBRARY_PATH: 这个环境变量指示动态连接器可以装载动态库的路径 (没有root权限也可以修改) 调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
静态库的搜索顺序 GCC命令中的参数-L---------->环境变量LIBRARY_PATH------>/lib /usr/lib /usr/local/lib 动态库搜素顺序 GCC命令中的参数-L---------->环境变量LD_LIBRARY_PATH---->/etc/ld.so.conf---->/lib---->/usr/lib
|
命令行参数讲完之后,还有一些linux的其他杂项,在这里罗列一下
应用程序 |
系统通用程序---->/usr/bin 在计算机安装的程序--->/usr/local/bin 或者/opt 个人用到的程序----->/usr/local |
头文件 | |
库函数 |
好了,以上就是所有的在linux下的动态静态库了.下一章节我们会就windows下的dll和lib进行话题展开.