一、什么是库
在 linux 平台存在着大量的库,库的本质是一种可执行的二进制代码(但不可以独立执行)集合,可以通过操作系统将其载入内存执行。windows 和 linux 的平台的差异性(预编译、编译器、汇编器和链接器的不同),因此二者库的二进制版本也是无法兼容。
二、 库的种类
linux 下的库有两种:静态库和动态库(共享库),他们的主要差别在被调用者调用时时代码被载入的时机不同。
静态库的特征:1、以.a为后缀;2、前缀名以lib开始;3、生成的可执行程序体积较大;4、代码在编译过程中就会被加载进可执行程序程序中。
共享库(动态库)的特征:1、用.so为后缀;2、前缀名以lib开始;3、生成的可执行程序代码体积较小;4、代码是在可执行程序运行中调用时才载入内存的,在编译过程中仅简单的引用。
共享库(动态库)的优势: 若在不同的应用程序中如果调用相同的库,那么只会在内存里存在一份共享库的实例即可;可以在系统中存在库的多个版本。使用者需要自己搞定共享库问题。
静态库的优势:若在不同的应用程序中如果调用相同的库,那么会在内存里存在多份共享库的实例;无需考虑库的问题。
为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如: libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。
ln -s libhello.so.1.31 libhello.so.1
ln -s libhello.so.1 libhello.so
代码如下:
#vector.h 1 #ifndef vector_h 2 #define vector_h 3 4 void addVec(int *xP, int *yP, int *zP, int Num); 5 void mulVec(int *xP, int *yP, int *zP, int Num); 6 7 #endif #addVec.c 1 #include "vector.h" 2 3 void addVec(int *xP, int *yP, int *zP, int Num){ 4 for(int i = 0; i < n; i++){ 5 zP[i] = xP[i] + yP[i]; 6 } 7 8 return; 9 } ~ #mulVec.c 1 #include "vector.h" 2 3 void mulVec(int *xP, int *yP, int *zP, int Num){ 4 for(int i = 0; i < n; i++){ 5 zP[i] = xP[i] * yP[i]; 6 } 7 8 return; 9 } #testVec.c 1 #include <stdio.h> 2 #include "vector.h" 3 4 int x[2] = {1, 2}; 5 int y[2] = {3, 4}; 6 int z[2]; 7 8 int main(int argc, char **argv) 9 { 10 addVec(x, y, z, 2); 11 printf("z = [%d %d] ", z[0], z[1]); 12 13 return 0; 14 }
三、生成静态库文件:
1、gcc -Og -c *Vec.c //生成目标文件
2、ls *.o 或 ls | grep .o //查看目标文件
3、ar crs libVector.a addVec.o mulVec.o //生成静态库文件
4、ls *.a //查看生成的库文件
5、file libVector.a //查看文件类型
6、ar -t libVector.a //查看文件内容
四、动态库的生成:
1、gcc -shared -fPIC -o libVector.so addVec.c mulVec.c //生成动态库文件
2、ls *.so 或 ls | grep .so //查看目标文件
3、file libVector.a //查看文件类型
4、ldd libVector.so //查看生成的库文件
5、nm libVector.so //查看生成的库文件包含哪些函数
五、库文件的其他
1、库文件的规范: linux中,库文件常放在/usr/lib和/lib下, 其中静态库名字常为libxxxx.a,其中xxxx是该库文件的名称;动态库的名字常为libxxxx.so,xxxx 是该库的名称,有的库会有主副等版本号
2、定位共享库的时机 :当系统加载可执行代码(即库文件)的时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径,此时就需要系统动态载入器(dynamic linker/loader) ,对linux下的elf 格式的可执行程序,是由 ld-linux.so* 来完成的,它先后搜索 elf 文件的 DT_RPATH 段-->环境变量LD_LIBRARY_PATH—->/etc/ld.so.cache 文件列表--> /lib/,/usr/lib 目录找到库文件后将其载入内存。如: export LD_LIBRARY_PATH=’pwd’ ,将当前文件目录添加为共享目录。
3、使用ldd工具,查看可执行程序依赖那些动态库或着动态库依赖于那些动态库:
ldd 命令可以查看一个可执行程序依赖的共享库,
例如 # ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
可以看到 ln 命令依赖于 libc 库和 ld-linux 库
1.7、使用nm工具,查看静态库和动态库中有那些函数名;
(T类表示函数是当前库中定义的,U类表示函数是被调用的,在其它库中定义的,W类是当前库中定义,被其它库中的函数覆盖)。
有时候可能需要查看一个库中到底有哪些函数,nm工具可以打印出库中的涉及到的所有符号,这里的库既可以是静态的也可以是动态的。
nm列出的符号有很多, 常见的有三种::
T类:是在库中定义的函数,用T表示,这是最常见的;
U类:是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
W类:是所谓的“弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
例如,假设开发者希望知道上文提到的hello库中是否引用了 printf():
nm libhello.so | grep printf
发现printf是U类符号,说明printf被引用,但是并没有在库中定义。
由此可以推断,要正常使用hello库,必须有其它库支持,使用ldd工具查看hello依赖于哪些库:
ldd libhello.so
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以深入研究
1.8、使用ar工具,可以生成静态库,同时可以查看静态库中包含那些.o文件,即有那些源文件构成。
可以使用 ar -t libname.a 来查看一个静态库由那些.o文件构成。
可以使用 ar q libname.a xxx1.o xxx2.o xxx3.o ... xxxn.o 生成静态库
1.9、file查看动态库和静态库是32位,还是64位下的库:
如果是动态库,可以使用file *.so;
如果是静态哭,可以使用objdump -x *.a