动态库与静态库的深入分析

关于动态与静态链接的总结:

1)静态库由ar命令创建,动态库由链接器创建,如果一个可执行文件只链接静态库,称它为静态链接
2)静态库是目标文件的存档,而动态库本身就是一个目标文件,例如:gcc----->hello.o---->ar------>libhello.a与gcc----->hello.so
3)静态库是将部分或全部的目标代码复制到可执行文件中,部分复制是指如果链接程序从存档文件中读取目标文件,如果只用到一个或两个目标文件,则只会复制用到的目标文件
  动态库不会复制任何目标代码到可执行文件中.只会记录有关的动态库信息.
4)静态链接的程序在运行期间不依赖于静态库
5)修复共享库更容易,只需修复共享库的错误,就能同时修复系统中所有使用该库的程序,如果是静态库,就要重建所有使用该库的程序.


以下有两个小测试:
测试1)演示动态链接库程序在运行时加载动态链接库的过程

================================================
实验:
/*新建程序hello.c*/
cat << EOF > hello.c
> #include <stdio.h>
> int main(){printf("Hello World\n");}
> EOF


/*新建一个空的程序empty.c*/
cat /dev/null > empty.c

/*采用共享库的方式链接*/
gcc -shared -fpic -o empty.so empty.c

/*-fpic 使输出的对象模块是按照可重定位地址方式生成的,使生成的代码是位置无关的,因为重建共享目标库需要位置无关,并且这类代码支持大的偏移。*/
/*-share 指定生成动态库*/

gcc -o hello hello.c empty.so

/*提示没有找到emtpy.so动态库*/
./hello
./hello: error while loading shared libraries: empty.so: cannot open shared object file: No such file or directory

/*指定LD_LIBRARY_PATH=.,并运行程序./hello*/
LD_LIBRARY_PATH=. ./hello
======================================================
最后可以运行了
解析:
1)程序在第一次运行失败是因为没有找到动态库文件empty.so.它是如何去找共享库的呢.
首先跟据环境变量LD_LIBRARY_PATH查找动态链接库文件empty.so,如果没有找到empty.so,它会到ld.so.cache中寻找empty.so
最后到 /lib和/usr/lib两个目录下寻找
如果没有找到empty.so,打印输出error信息,并退出
这里我们执行export LD_LIBRARAY_PATH=/etc,然后执行strace跟踪执行流程:
execve("./hello", ["./hello"], [/* 38 vars */]) = 0
uname({sys="Linux", node="testdb", ...}) = 0
brk(0)                                  = 0x8361000
open("/etc/ld.so.preload", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/etc/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/i686", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/etc/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/mmx", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/etc/tls/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/etc/tls", 0xbfff9c10)          = -1 ENOENT (No such file or directory)
open("/etc/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/i686/mmx", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/etc/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/i686", 0xbfff9c10)         = -1 ENOENT (No such file or directory)
open("/etc/mmx/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/etc/mmx", 0xbfff9c10)          = -1 ENOENT (No such file or directory)
open("/etc/libempty.so", O_RDONLY)      = -1 ENOENT (No such file or directory)
stat64("/etc", {st_mode=S_IFDIR|0755, st_size=8192, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/i686", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/lib/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/mmx", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/lib/tls/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/lib/tls", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/lib/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/lib/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/lib/mmx/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbfff9c10)          = -1 ENOENT (No such file or directory)
open("/lib/libempty.so", O_RDONLY)      = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/usr/lib/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/i686", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/mmx", 0xbfff9c10)  = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/usr/lib/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/mmx", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/usr/lib/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/usr/lib", {st_mode=S_IFDIR|0755, st_size=53248, ...}) = 0
writev(2, [{"./hello", 7}, {": ", 2}, {"error while loading shared libra"..., 36}, {": ", 2}, {"libempty.so", 11}, {": ", 2}, {"cannot open shared object file", 30}, {": ", 2}, {"No such file or directory", 25}, {"\n", 1}], 10./hello: error while loading shared libraries: libempty.so: cannot open shared object file: No such file or directory
) = 118
exit_group(127)                         = ?


结果表明:hello程序会先到/etc目标下找,
然后打开ld.so.cache,寻找它需要的共享库
open("/etc/ld.so.cache", O_RDONLY)      = -1 ENOENT (No such file or directory)
最后到/lib和/usr/lib下寻找它的共享库
它的共享库包括:
ldd hello
 libempty.so => not found
 libc.so.6 => /lib/tls/libc.so.6 (0x00e8e000)
 /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x004bf000)

OK,这里需要说明的是采用gcc -o hello hello.c empty.so进行编译的程序,ld.so.cache是不会帮助程序找到empty.so共享库的.
因为是ldconfig只会搜索以lib开头以.so为后缀的共享库文件.如果采用empty.so为共享库文件.在ld.so.conf中指定其路径,ldconfig也不会将其写到ld.so.cache中
=========================================
实验:
export LD_LIBRARY_PATH=.
./hello
=========================================
这里执行成功后,在ld.so.cache中也看不到empty.so的身影.
=========================================
实验:
strings /etc/ld.so.cache|grep empty.so
=========================================
将empty改名为libempty.so,再重新进行编译链接
=========================================
实验:
unset LD_LIBRARY_PATH
mv empty.so libempty.so
pwd >> /etc/ld.so.conf
ldconfig
gcc -o hello hello.c libempty.so
strings /etc/ld.so.cache|grep empty.so
=========================================

更好的用法是使用-l选项.
例如:gcc -o hello hello.c -L . -lempty
其中-L 是指定搜索的目标,否则指示找不到libempty.so

指定'-l'选项和指定文件名的唯一区别是,-l 选项用'lib'和'.so'或'.a'把 library 包裹起来,而且搜索一些目录.
这里指的搜索一些目录是-L指定的目录

现在搜索的顺序是
1)变量LD_LIBRARY_PATH指定的路径
2)ld.so.cache中包含的lib库文件名及路径
3)到/lib和/usr/lib/中搜寻

那么在ld.so.cache在什么时候加载了libempty.so文件呢?
答案是用产生libempty.so文件后,再执行ldconfig,最后生成的
产生libempty.so产生的方式可以是拷贝或是gcc -shared等方式,而ldconfig命令是跟据/etc/ld.so.conf中的内容生成ld.so.cache
ldconfig会遍列ld.so.conf指定的所有路径,查找到以lib开头并以.so为后缀的ELF文件
如果libempty.so被删除,再执行ldconfig,就会将失效的记录删除掉


结论:
1)LD_LIBRARY_PATH和ld.so.conf及ld.so.cache毫无关系.
2)只有采用以lib开头以.so为后缀的共享库才会被ld.so.cache使用,用-l 选项和直接用文件的区别是-l选项会用'lib'和'.so'把 library 包裹起来,
而直接用文件则要明显的指定文件开头为lib和后缀为.so的共享库文件.如果不指定,ldconfig是不会利用这个共享库的.
3)ld.so.cache是用ldconfig产生,而ldconfig会根据ld.so.conf搜索所有的lib库
4)搜索共享库的顺序是,先找LD_LIBRARY_PATH指定的路径,然后找ld.so.cache指定的路径,最后到/lib和/usr/lib下找
5)搜索/lib/和/usr/lib中的共享库文件,并不是只要在这两个目录下就可以,它是搜索固定的子目录,比如/usr/lib/i686/mmx/等等,最后再搜索/lib/和/usr/lib
6)产生新的hello文件后,可以移动 libempty.so共享库文件,但前提是只能把它移动到hello程序可以找到的地方.
也就是上面三个搜索目标(LD_LIBRARY_PATH,ld.so.cache,lib和/usr/lib)
7)ldconfig只会搜索以lib开头以.so为后缀的共享库文件,而不会搜索以lib开头,以.a为后缀的静态库文件.

 

测试2:
用下面三种链接方式进行编译分别进行测试:
第一种把它作为两个目标链接起来,这个方法最简单.
第二种方法是把目标方文件以静态库的方式进行链接.
第三种方法是以共享目标文件的方式进行链接.

 

1)新建一个名为piggy.c的共享目标文件,它什么都不做,但会占用内存空间,[0x10000]声明了1MB的全局缓冲区
实验:
=========================================
cat << EOF > piggy.c
>char bank[0x10000];
>EOF

cat << EOF > hello.c
> #include <unistd.h>
> int main(){pause();}
> EOF
=========================================


1.1编译链接:
=========================================
实验:
gcc -o hello hello.c piggy.c
=========================================


1.2)查看内存的占用情况:
=========================================
实验:
size ./hello
   text    data     bss     dec     hex filename
   1136     520   65568   67224   10698 ./hello
=========================================


1.3)用nm命令显示已存在的bank数据,显示占用了1MB的内存空间
=========================================
实验:
nm -S hello|grep bank
080495a0 00100000 B bank
=========================================

 

2)用静态链接库的方法,进行链接
2.1)生成.o的目标文件
=========================================
实验:
gcc -c piggy.c
=========================================


2.2)用ar命令创建目标文件的存档libpig.a
=========================================
实验:
ar clq libpig.a piggy.o
=========================================


2.3)链接静态库,生成可执行文件hello
=========================================
实验:
gcc -o hello hello.c -L ./ -lpig --static
=========================================


2.4)查看内存的占用情况:
=========================================
实验:
size ./hello
   text    data     bss     dec     hex filename
 656930    3488   12568  672986   a44da ./hello
=========================================


2.5)用nm命令显示是否包括bank数组
=========================================
实验:
nm -S hello|grep bank
=========================================
用静态库链接的可执行程序,可执行文件的大小有了明显的增加.但是没有包括数组bank.

 


3)用动态链接库的方法,进行链接

3.1)生成动态函数库 piggy.so
=========================================
实验:
gcc -shared -o piggy.so piggy.c
=========================================


3.2)链接动态库,生成可执行文件hello
=========================================
实验:
gcc -o hello hello.c  ./piggy.so
=========================================


3.3)查看内存的占用情况:
=========================================
实验:
size ./hello
   text    data     bss     dec     hex filename
   1411     536      16    1963     7ab ./hello
=========================================


3.4)用nm命令显示是否包括bank数组
=========================================
实验:
nm -S hello|grep bank
=========================================
用动态库链接的可执行程序,比静态库的可执行程序有了明显的减小.并且没有看到数组bank


3.5)最后用pmap命令来运行以查看进程内存的映射:
=========================================
实验:
./hello &
 jobs -x pmap -q %1
4281:   ./hello
00111000   1176K r-x--  /lib/tls/libc-2.3.4.so
00237000      8K r-x--  /lib/tls/libc-2.3.4.so
00239000      8K rwx--  /lib/tls/libc-2.3.4.so
0023b000      8K rwx--    [ anon ]
009e7000      4K r-x--  /tmp/piggy.so (deleted)
009e8000      4K rwx--  /tmp/piggy.so (deleted)
009e9000   1024K rwx--    [ anon ]
00be9000     88K r-x--  /lib/ld-2.3.4.so
00bff000      4K r-x--  /lib/ld-2.3.4.so
00c00000      4K rwx--  /lib/ld-2.3.4.so
08048000      4K r-x--  /tmp/hello (deleted)
08049000      4K rw---  /tmp/hello (deleted)
b7ef1000      8K rw---    [ anon ]
bff78000    544K rw---    [ stack ]
ffffe000      4K -----    [ anon ]
==========================================
输出结果表明有1MB的匿名映射,是给bank数组分配的存储空间.


结论:
第一种方式会占用更多的内存.例如piggy.c中定义1MB的数组,在运行时它会占用1MB的内存空间,但由于采用的是动态链接方式,二进制文件会比轻小.
第二种方式则不会包括piggy.c中定义的1MB数组,链接器能够确定没有引用piggy.o,所以不会把它链接进来,但由于采用的是静态链接,二进制文件会比较大.
第三种方式程序的bss最小,不会加载1MB的数组,但在运行期间同样会占用1MB的内存空间.

这里有一点要说明.编译器默认会优先选择动态链接,如果只想链接静态目标,则必须在gcc/g++中写明-static选项.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值