- 2022-06-09 修改文章中GCC编译器的部分内容;
一、什么是库?
在windows平台和linux平台下都大量存在着库。一般是软件作者为了发布方便、替换方便或二次开发目的,而发布的一组可以单独与应用程序进行compile time或runtime链接的二进制可重定位目标码文件。
本质上来说库是一种可执行代码的二进制形式,这个文件可以在编译时由编译器直接链接到可执行程序中,也可以在运行时由操作系统的runtime enviroment根据需要动态加载到内存中。
一组库,就形成了一个发布包,当然,具体发布多少个库,完全由库提供商自己决定。
由于windows和linux的本质不同,因此二者库的二进制是不兼容的。
现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
二、库的分类
库有两种:静态库和动态库(共享库)。
windows平台下,静态库通常后缀为 .lib,动态库为 .dll;
linux平台下,静态库通常后缀为 .a,动态库为 .so;
从本质上来说,由同一段程序编译出来的静态库和动态库,在功能上是没有区别的。不同之处仅仅在于其名字上,也就是“静态”和“动态”。
二者均以文件的形式存在,其本质上是一种可执行代码的二进制格式,可以被载入内存中执行。无论是动态链接库还是静态链接库,它们无非是向其调用者提供变量、函数和类。
1.静态库
所谓静态库,就是在静态编译时由编译器到指定目录寻找并且进行链接,一旦链接完成,最终的可执行程序中就包含了该库文件中的所有有用信息,包括代码段、数据段等。
2.动态库
所谓动态库,就是在应用程序运行时,由操作系统根据应用程序的请求,动态到指定目录下寻找并装载入内存中,同时需要进行地址重定向。
3.区别
我们以编译链接、载入时刻两点来讨论静态库和动态库的区别。
编译链接:
静态链接库在程序编译时会被链接到目标代码中,目标程序运行时将不再需要改动库,移植方便,体积较大,浪费空间和资源,因为所有相关的对象文件与牵涉到库都被链接合成一个可执行文件,这样导致可执行文件的体积较大。
动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因为可执行文件体积较小。有了动态库,程序的升级会相对比较简单,比如某个动态库升级了,只需要更换这个动态库的文件,而不需要去更换可执行文件。但要注意的是,可执行程序在运行时需要能找到动态库文件。可执行文件是动态库的调用者。
载入时刻
二者的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
4.优缺点
相对于动态库,静态库的优点在于直接被链接进可执行程序中,之后,该可执行程序就不再依赖于运行环境的设置了(当然仍然会依赖于 CPU指令集和操作系统支持的可执行文件格式等硬性限制)。
而动态库的优点在于,用户甚至可以在程序运行时随时替换该动态库,这就构成了动态插件系统的基础。具体使用静态库和动态库,由程序员根据需要自己决定。
三、库文件的制作
1.库文件的命名
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称。动态库的名字一般为libxxxx.so.x.y.z,含义如下所示:
libname.so.x.y.z
lib | name | .so | .x | .y | .z |
---|---|---|---|---|---|
固定代表共享库 | 共享库名称 | 固定后缀 | 主版本号 | 次版本号 | 发行版本号 |
2.制作库文件常用参数
首先需要了解gcc编译器常用的一些参数,很重要。
参数 | 含义 |
---|---|
-shared | 指定生成动态链接库 |
-static | 指定生成静态链接库 |
-fPIC/ fpic | 表示编译为位置独立的代码,用于编译共享库。目标文件需要创建成位置无关码,概念上就是在可执行程序装载它们的时候,它们可以放在可执行程序内存的任何地方 |
-L (大写library) | 表示要链接的库在当前目录中 |
-I (library) | 指定需要连接的动态库。编译器查找动态链接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称 |
-Wall | 生成所有警告信息 |
-ggdb | 尽可能的生成gdb可用的调试信息 |
-g | 编译器在编译时产生调试信息,可以被调试器调试 |
-D | 在程序编译的时候,指定一个宏 |
-w | 不生成任何警告信息 |
-E | 预处理指定的源文件,不进行编译 |
-S | 编译指定的源文件,但是不进行汇编 |
-c | 只产生与处理、编译、汇编之后的代码,不链接,结果为.o文件 |
-o | 将文件编译成可执行文件 |
-std | 指定C方言,如:-std=c99,GCC默认的方言是GNU C |
3.库源文件
编写calc.c文件:
1 | int add(int a, int b) |
calc.h文件:
1 | int add(int, int); |
4.制作静态库并使用
(1) 需要把calc.c编译成.o文件
1 | gcc -c calc.c |
(2) 使用归档命令 ar (archive)将 .o 文件打包,生成静态库 libcalc.a
1 | ar -rcs libcalc.a calc.o |
r - 将文件插入备存文件中
c - 建立备存文件
s - 索引
(3) 使用静态库libcalc.a,只需要包含 calc.h 就可以使用函数 add() 和 sub()
1 |
|
静态库文件可以放在任意的位置,编译时只需要找到该文件即可。
1 | gcc main.c -o main libcalc.a |
(4) 库和头文件如果在其他目录下,使用以下命令编译
1 | gcc -c -l /home/xxxx/include test.c //假设test.c要使用对应的静态库 |
或者:
1 | gcc -c -I /home/xxxx/include -L /home/xxxxx/lib libcalc.a test.c |
1). 通过-I(是大i)指定对应的头文件
2). 通过-L制定库文件的路径,libcalc.a就是要用的静态库。
3). 在test.c中要包含静态库的头文件。
5.制作动态库并使用
(1) 把 calc.c 编译成动态链接库 libcalc.so
1 | gcc -fPIC -o libcalc.o -c calc.c |
也可以直接使用一条命令
1 | gcc -fPIC -shared -o libcalc.so calc.c |
(2) 动态库的安装,通常动态库拷贝到 /lib 下即可
1 | sudo cp libcalc.so /lib |
(3) 使用动态库
1 |
|
编译动态库:
1 | gcc main.c -o main -lcalc |
编译时动态库的名字与库文件对应关系
1 | libcalc.so ---------- -lcalc |
去掉 .so,lib简化成l,其他字母保留。