一 概述:
(一)目标文件的三种形式:
*可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
*共享目标文件:一种特殊的可重定位目标文件,可以在加载或者允许时被动态的加载到存储器并链接。
*可执行目标文件:包含二进制和数据,其形式可以被直接拷贝到存储器执行。
编译器和汇编器生成可重定位目标文件(包括共享目标文件),链接器生成可执行目标文件。
各个系统之间,目标文件格式都不相同。现在Unix系统(包括linux)使用的是Unix可执行和可链接格式(Executable and Linkable Format,ELF)。
二 静态库与共享库
(一)静态库和动态库使用:
(1)在linux下,库文件一般放在/usr/lib和/lib下:
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号
自己创建的库要复制到/usr/lib或/lib目录下,ld默认才能找到。放到/usr/lib或/lib目录下后,编译时仍然要指明链接的库的名字,只是不需要指明路径而已。当静态库和动态库重名时,gcc优先选择动态库。
*-L 及-l 参数放在后面.其中,-L 加载库文件路径,-l 指明库文件名字.
例如:把自己的库libmylib.so或libmylib.a复制到usr/lib或/lib目录下后,编译时仍需指明要链接的库。gcc -o main main.c -lmylib
(2)当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然 而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库,所以删除动态库后执行程序会出现错误:找不到共享库。
(3)静态库与动态库的搜索顺序:
*静态库链接时搜索路径顺序:
1.ld会去找GCC命令中的参数-L
2.再找gcc的环境变量LIBRARY_PATH
3.再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的
*动态链接时、执行时搜索路径顺序:
1.编译目标代码时指定的动态库搜索路径(-L编译可通过但运行部通过需设置2-5任一。);
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib。
(4)Linux操作系统上面的动态共享库大致分为三类:
1.操作系统级别的共享库和基础的系统工具库 :
*这些系统库放在/lib和/usr/lib目录下。
2.应用程序级别的系统共享库 :
*并非操作系统自带,但是可能被很多应用程序所共享的库,一般会被放在/usr/local/lib和/usr/local/lib64这两个目录下面。
3.应用程序独享的动态共享库 :
*有很多共享库只被特定的应用程序使用,那么就没有必要加入系统库路径,以免应用程序的共享库之间发生版本冲突。因此Linux还可以通过设置环境变量LD_LIBRARY_PATH来临时指定应用程序的共享库搜索路径。可以在应用程序的启动脚本里面预先设置LD_LIBRARY_PATH,指定本应用程序附加的共享库搜索路径,从而让应用程序找到它。
(5)注意:
*编译时:gcc+源文件+链接库(-L+-l),顺序不能颠倒。
(二)静态库(linux静态库的形式:libxxx.a):
在Unix系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中,存档文件名由后缀.a标示。
gcc的-static参数告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,它可以加载到存储器并运行,在加载时无需更进一步的链接。
静态库的使用
静态库的操作工具:gcc和ar 命令。
编写及使用静态库
(1)设计库源码 pr1.c 和 pr2.c
[root@billstone make_lib]# cat pr1.c
void print1()
{
printf("This is the first lib src!\n");
}
[root@billstone make_lib]# cat pr2.c
void print2()
{
printf("This is the second src lib!\n");
}
(2) 编译.c 文件
[bill@billstone make_lib]$ cc -O -c pr1.c pr2.c
[bill@billstone make_lib]$ ls -l pr*.o
-rw-rw-r-- 1 bill bill 804 4 月 15 11:11 pr1.o
-rw-rw-r-- 1 bill bill 804 4 月 15 11:11 pr2.o
(3) 链接静态库
为了在编译程序中正确找到库文件,静态库必须按照 lib[name].a 的规则命名,如下例中[name]=pr.
[bill@billstone make_lib]$ ar -rsv libpr.a pr1.o pr2.o
a - pr1.o
a - pr2.o
[bill@billstone make_lib]$ ls -l *.a
-rw-rw-r-- 1 bill bill 1822 4 月 15 11:12 libpr.a
[bill@billstone make_lib]$ ar -t libpr.a
pr1.o
pr2.o
(4) 调用库函数代码 main.c
[bill@billstone make_lib]$ cat main.c
int main()
{
print1();
print2();
return 0;
}
(5) 编译链接选项
-L 及-l 参数放在后面.其中,-L 加载库文件路径,-l 指明库文件名字.
[bill@billstone make_lib]$ gcc -o main main.c -L./ -lpr
[bill@billstone make_lib]$ ls -l main*
-rwxrwxr-x 1 bill bill 11805 4 月 15 11:17 main
-rw-rw-r-- 1 bill bill 50 4 月 15 11:15 main.c
(6)执行目标程序
[bill@billstone make_lib]$ ./main
This is the first lib src!
This is the second src lib!
[bill@billstone make_lib]$
(三)共享库(Unix中通常以.so后缀来表示):
静态库的缺点:
*静态库需要定期维护和更新,如果程序员要使用一个库的最近版本,就必须以某种方式了解该库的更新情况,然后显式地将他们的程序与更新了的库重新链接。
*几乎每个C程序都使用标准I/O函数,如printf和scanf。在运行时,这些函数代码会被复制到每个运行进程的文本段中。在一个运行50~100个进程的典型系统上,将是对稀缺的存储器系统资源的极大浪费。
共享库以两种形式来实现共享:
*首先,在给定文件系统中,对于一个库只有一个.so文件,所有引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样被拷贝和嵌入到引用它们的可执行的文件中。
*其次,在存储器中,一个共享库的.text节的一个副本可以被不同的正在运行的进程共享。
如下图:没有任何libvector.so的代码和数据节真的被拷贝到可执行文件p2中,反之,链接器只拷贝了一些重定位和符号表信息。
动态库的使用
编写动态库
(1)设计库代码
[bill@billstone make_lib]$ cat pr1.c
int p = 2;
void print(){
printf("This is the first dll src!\n");
}
[bill@billstone make_lib]$
(2)生成动态库
[bill@billstone make_lib]$ gcc -O -fpic -shared -o dl.so pr1.c
[bill@billstone make_lib]$ ls -l *.so
-rwxrwxr-x 1 bill bill 6592 4 月 15 15:19 dl.so
[bill@billstone make_lib]$
动态库的隐式调用
在编译调用库函数代码时指明动态库的位置及名字, 看下面实例
[bill@billstone make_lib]$ cat main.c
int main()
{
print();
return 0;
}
[bill@billstone make_lib]$ gcc -o tdl main.c ./dl.so
[bill@billstone make_lib]$ ./tdl
This is the first dll src!
[bill@billstone make_lib]$
当动态库的位置活名字发生改变时, 程序将无法正常运行; 而动态库取代静态库的好处之一则是通过更新动态库而随时升级库的内容.
ldd命令可以打印出某个可执行文件依赖的共享库。
(3)应用程序还可能在运行时要求动态链接器加载和链接任意共享库,而无需在编译时链接这些库到应用中。