Linux下静态库和动态库简介及示例
Category: 技术学习 | Tags: linux, c语言 | Source: Markdown ----------> Back to Wiki在实际的软件开发项目中,不是每一行代码都需要我们亲自写。在我们的软件产品中,有一些代码(尤其是函数)的出现频率很高,它们可以被当作公共代码来反复使用。为了避免重复劳动,我们就把这些公共代码编译为库文件,供需要的程序调用。在Linux
中,库分为静态库和动态库两种。
简介
众所周知,程序一般需要经过预处理、编译、汇编和链接这几个步骤才能变成可执行的程序。在实际的软件开发中,对于一些需要被许多模块反复使用的公共代码,我们就将它们编译为库文件。
库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。Linux
支持的库分为静态库和动态库,动态库又称共享库。一般说来,Linux
中的一些重要的库是存放在lib
目录下的。
静态库文件的后缀为
.a
,在Linux
下一般命名为libxxx.a
。在链接步骤中,连接器将从静态库文件中取得所需的代码,复制到生成的可执行文件中。因此,整个库中的所有函数都被编译进了目标代码中。
动态库文件的后缀为
.so
,在Linux
下一般命名为libxxx.so
。相对于静态库,动态库在编译的时候并没有被编译进目标代码中,而是程序执行到相关函数时才调用库中对应的函数。
可以看到,静态库的优点是编译后的执行程序不需要外部的函数库支持,缺点是如果静态函数库改变了,那么你的程序必须重新编译;而动态库在多个应用程序都要使用同一函数库的时候就非常适合,但前提是程序的运行环境中必须提供相应的库。 不管是静态库,还是动态库,都是由*.o
目标文件生成的。
静态库
~/test> ll
-rw------- 1 neal neal 96 Nov 4 16:11 main.c
-rw------- 1 neal neal 70 Nov 4 16:04 test.h
-rw------- 1 neal neal 84 Nov 4 16:04 test_1.c
-rw------- 1 neal neal 84 Nov 4 16:04 test_2.c
-rw------- 1 neal neal 84 Nov 4 16:04 test_3.c
~/test> gcc -c test_1.c test_2.c test_3.c
~/test> ll
-rw------- 1 neal neal 96 Nov 4 16:11 main.c
-rw------- 1 neal neal 70 Nov 4 16:04 test.h
-rw------- 1 neal neal 84 Nov 4 16:04 test_1.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_1.o
-rw------- 1 neal neal 84 Nov 4 16:04 test_2.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_2.o
-rw------- 1 neal neal 84 Nov 4 16:04 test_3.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_3.o
~/test> ar -r libtest.a test_1.o test_2.o test_3.o
ar: creating libtest.a
~/test> ll
-rw------- 1 neal neal 96 Nov 4 16:11 main.c
-rw-rw-rw- 1 neal neal 5158 Nov 4 16:15 libtest.a
-rw------- 1 neal neal 70 Nov 4 16:04 test.h
-rw------- 1 neal neal 84 Nov 4 16:04 test_1.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_1.o
-rw------- 1 neal neal 84 Nov 4 16:04 test_2.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_2.o
-rw------- 1 neal neal 84 Nov 4 16:04 test_3.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_3.o
~/test> gcc -o test main.c libtest.a
~/test> ll
-rw------- 1 neal neal 96 Nov 4 16:11 main.c
-rwxrwxrwx 1 neal neal 12008 Nov 4 16:16 test
-rw-rw-rw- 1 neal neal 5158 Nov 4 16:15 libtest.a
-rw------- 1 neal neal 70 Nov 4 16:04 test.h
-rw------- 1 neal neal 84 Nov 4 16:04 test_1.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_1.o
-rw------- 1 neal neal 84 Nov 4 16:04 test_2.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_2.o
-rw------- 1 neal neal 84 Nov 4 16:04 test_3.c
-rw-rw-rw- 1 neal neal 1624 Nov 4 16:15 test_3.o
~/test> ./test
this is in test_1......
this is in test_2......
this is in test_3......
我们可以看到,生成静态库文件的命令是ar -r libtest.a test_1.o test_2.o test_3.o
,而将静态库文件编译进代码的命令是gcc -o test main.c libtest.a
。
这样生成了静态库文件libtest.a
之后,如果还有其他程序要调用test_1.c
、test_2.c
、test_3.c
中实现的函数,只需要将test.h
和libtest.a
拷贝到对应的代码工程中,然后执行类似gcc -o test main.c libtest.a
这样的命令即可。
动态库
~/test> ll
-rw------- 1 neal neal 70 Nov 5 13:44 so_test.h
-rw------- 1 neal neal 105 Nov 4 15:25 test.c
-rw------- 1 neal neal 84 Nov 4 15:25 test_a.c
-rw------- 1 neal neal 84 Nov 4 15:25 test_b.c
-rw------- 1 neal neal 84 Nov 4 15:25 test_c.c
~/test> gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
~/test> gcc test.c -L. -ltest -o test
~/test> ll
-rwxrwxrwx 1 neal neal 8309 Nov 5 13:46 libtest.so
-rw------- 1 neal neal 70 Nov 5 13:44 so_test.h
-rwxrwxrwx 1 neal neal 11883 Nov 5 13:46 test
-rw------- 1 neal neal 105 Nov 4 15:25 test.c
-rw------- 1 neal neal 84 Nov 4 15:25 test_a.c
-rw------- 1 neal neal 84 Nov 4 15:25 test_b.c
-rw------- 1 neal neal 84 Nov 4 15:25 test_c.c
~/test> ./test
this is in test_a...
this is in test_b...
this is in test_c...
注意,./test
命令执行成功的前提是在环境变量中添加了.so
文件所在的路径,这个路径可以在.bash_profile
文件的LD_LIBRARY_PATH
变量的值中添加。
我们可以看到,多个文件生成动态库文件的命令是gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
,而将动态库文件编译进代码的命令是gcc test.c -L. -ltest -o test
(-L.
表示当前路径)。
这样生成了动态库文件libtest.so
之后,如果还有其他程序要调用test_a.c
、test_b.c
、test_c.c
中实现的函数,只需要将so_test.h
和libtest.so
拷贝到对应的代码工程中,然后执行类似gcc test.c -L. -ltest -o test
这样的命令即可(前提是libtest.so
所在的路径在环境变量中设置正确)。
总结
有关生成静态库和动态库的命令,说明如下:
第一,在本文中,我们使用的生成静态库的命令形如ar -r test.a test.o
,其中,-r
是replace
的意思,表示如果当前插入的模块名已经在库中存在,则替换同名的模块。我们也可以用形如ar -cr test.a test.o
的命令来生成静态库,其中-c
是create
的意思,表示生成。
第二,在本文中,我们使用的生成动态库文件的命令形如gcc test_a.c -fPIC -shared -o libtest.so
,其中,fPIC
表示编译为位置独立的代码,shared
表示生成的库为共享库。将动态库文件编译进代码的命令是gcc test.c -L. -ltest -Wl,-rpath=. -o test
,-L
指定库查找的位置(注意L
后面还有'.'
),表示在当前目录下查找(如果在当前目录下的lib
目录下查找,可以写成-L./lib
);-l
则指定函数库名,其中的lib
和.so
省略(如这里的libtest.so
就简写为test
);-Wl,-rpath=.
则指定程序运行时优先查找的库路径,这里表示优先查找当前目录是否有libtest.so
(如果加上这个选项,你可以不必设置LD_LIBRARY_PATH
这个环境变量)。
第三,使用ldd
命令可以查看一个可执行程序依赖的共享库,该命令的使用示例如下所示:
~/test> ldd test
linux-vdso.so.1 => (0x00007fff1db6e000)
libtest.so => ./libtest.so (0x00007fdbfff21000)
libc.so.6 => /lib64/libc.so.6 (0x00007fdbffb95000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdc00124000)
可以看到,可执行文件test
依赖于四个共享库,其中libtest.so
位于当前目录下。
第四,虽然普通编程用不到,但想反汇编的话绝对少不了目标文件格式分析工具:
ar
命令可以用来创建,修改和提取静态库;nm
命令可以列出object
文件的符号表;objdump
命令用来查看和反汇编object
文件,程序破解必备工具;objcopy
命令用来拷贝出object
文件的特定部分,甚至将object
文件转换为binary
格式;readelf
命令可以查看ELF
格式文件的符号表。