重温C语言 - 静态库与动态库
由C程序编译流程,我们知道,一个C程序分为若干程序文件,每个程序文件单独编译生成目标文件,最后将所有目标链接,如果所有的函数符号和变量符号都找到正确地址则编译成功,生成可执行文件。那就有个问题了,如果目标文件过多,则必须记住,主程序文件涉及了那些模块,用到了其中的变量或函数,要把那些目标文件链接进来,才能正确编译。为了解决这个问题,C程序引入了库的概念,所谓库就是一组已经编译好的目标文件集合,链接的时候只要把这个库文件指示给链接程序,链接程序会自动从文件中查找符号要求的函数和变量进行链接,整个查找过程根本不需要我们担心。
##一、静态库 静态库的结构比较简单,就是把原来的目标代码放在一起,链接的时候根据每一份目标代码的符号表 查找相应的符号(函数和变量的名字),找到话就把该函数里面需要定位的符号进行定位,然后将整 个函数放到可执行文件里。如果找不到需要的函数就报错。
// 1.c
extern int global;
void f();
int main()
{
f();
++global;
return 0;
}
//2.c
extern void g();
int global = 10;
void f()
{
g();
}
//3.c
static int local = 1;
void g()
{
--local;
}
gcc 1.c 2.c 3.c
直接完成编译连接,生成a.out。现在把1.c 中用到的2.c 3.c做成静态库的方式。然后用nm命令查看目标文件
[tank@gopherbc c]$gcc -c 1.c 2.c 3.c
[tank@gopherbc c]$nm 1.o
U f #符号f未定义
U global #符号global未定义
00000000 T main
[tank@gopherbc c]$nm 2.o
00000000 T f #全局函数f定义
U g #符号g未定义
00000000 D global #extern全局变量global定义
[tank@gopherbc c]$nm 3.o
00000000 T g #全局函数g定义
00000000 d local #static全局变量local定义
将2.o 3.o 生成静态库libtest.a,然后编译
[tank@gopherbc c]$ ar rc libtest.a 2.o 3.o
[tank@gopherbc c]$ gcc 1.c -L . -ltest
查看下生成的可执行文件
[tank@gopherbc c]$ nm a.out
.....................
08048364 T f
...
08048374 T g
08049560 D global
...
08049564 d local
......................
可以看出链接以后,所有符号都有确定地址,链接成功,生成可执行代码。总的来说链接过程是 这样的,main中调用函数f, 中生成汇编代码时候就是call f, f是个符号地址,于是链接的时候, 去库里找符号f定义,找到了把的符号f 定义,它义代表一段代码,于是把这段代码复制到可执行 文件,得到一个确定的地址faddress, 然后填充调用指令call f变成call faddress.然后递归查 看f中需要定位的符号,知道所有符号完成定位。
静态库的特点
- 链接后产生的可执行文件包含了所有需要调用函数的代码,因此占用空间比较大
- 如果有多个调用相同库函数的进程在内存中同时进行,内存村在多份相同库函数的代码
##二、动态库
动态库就是在程序载入内存的时候才真正把函数代码链接进来确定他们的地址,并且就算多 个程序运行,内存也只存在一份代码。由于动态库的代码必须满足这样一个条件:能被加载 到不同进程的不同地址,所以代码必须经过特别的编译处理。我们把这种特殊处理的代码叫 做“位置无关的代码(Position Idpendent Code, PIC)”.
//4.c
int fun(void);
int main()
{
fun();
return 0;
}
//5.c
int fun(void)
{
reurn 0;
}
生成动态库libfun.so
$ gcc -c -fPIC 5.c
$ gcc -shared -o libfun.so 5.o
$ gcc 4.c -L. -lfun
$ ldd a.out
libfun.so => not found
libc.so.6 => /lib64/tls/libc.so.6 (0x0000003cf5900000)
/lib64/ld-linux-x86-64.so.2 (0x0000003cf5500000)
生成了可执行文件,但libfun.so并没有找到。系统要使用动态库,必须让系统知道放置动态库 的位置,最简单的方法就是设置LD_LIBRARY_PATH环境变量
$ export LD_LIBRARY_PATH=~/test/deepc/
查看符号信息
$ nm a.out
00000000004005a0 t frame_dummy
0000000000400780 r __FRAME_END__
U fun
0000000000500958 A _GLOBAL_OFFSET_TABLE_
生成的可执行文件a.out的符号里面指出fun的地址还没有确定(用U表示undefined),如果是静态 库,那么fun的地址在链接的时候就能准确计算并记录的a.out的符号表中,这充分说明fun的地址等 到程序运行才算出来。当我们运行a.out,载入程序a.out调用动态库代码,首先在内存中查找是否有 相同的库代码在运行,如果有把已经运行的代码地址(转换成逻辑地址后)放到某张表里面,调用库 代码的语句从这张表知道库代码地址正确调用。如果内存没有相同的库代码运行,在载入程序通过计 算确定其逻辑地址,再把库代码载入,并把逻辑地址写到哪张表里,完成整个装载过程。
Simple is beautiful博客,转载请注明出处