在实践中,我们一定会使用别人的库(不限于C、C++的库),在实践中,我们会使用成熟、被广泛使用的第三方库,而不会花费很多时间自己造轮子,为了能更好地使用库,就要在学习阶段了解其本质。那么对于库而言,可以从两方面认识它:
预处理: 头文件展开、去注释、宏替换、条件编译等,生成.i文件
编译: 词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,生成.s文件
汇编: 将汇编指令转换成二进制指令,生成.o文件
链接: 将生成的各个.o文件进行链接,生成可执行程序(Windows:.exe,Linux:.out)
例如: 用test1.c,test2.c以及main1.c形成可执行文件,需要先得到各个文件的目标文test1.o,test2.o以及main1.o,然后将这些目标文件链接起来,最终形成一个可执行程序。
实际上,对于可能频繁用到的源文件,比如这里的test1.c test2.c 可以将他们的目标文件test1.o,test2.o进行打包,之后需要用到这两个目标文件就可以直接链接这个包当中的目标文件即可,上面的打包就可以称为一个库。库的本质就是一堆.O文件集合,库的文件当中并不包含主函数而只是包含了大量写好的方法以供调用,因此,我们说动静态库是可执行程序的"半成品" -》所以库中没有main函数
站在编写者角度:生成静态库
以下面四个文件和一个main.c文件为例,演示其打包为库的过程
1 int Add(int x, int y)
2 {
3 return x + y;
4 }
Add.h
extern int Add(int x, int y);
Sub.c
1 int Sub(int x, int y)
2 {
3 return x - y;
4 }
Sub.h
extern int Sub(int x, int y);
gcc -o Test Add.c Sub.c main.c
//我们使用 gcc -o 选项将所有的源文件进行编译,形成可执行
我们不建议上述方法,建议是将所有的源文件编译成.o文件,再将所有的.o文件链接形成我们的可执行
在 Makefile 中,$< 是一个自动化变量,表示规则中的第一个依赖文件(prerequisite)。
通过使用 $<,你可以方便地引用规则中的第一个依赖文件,使规则更加灵活和通用。
gcc Add.o Sub.o main.o -o Test
//将别人的.o文件与自己写的main.c文件形成的.o文件链接起来,形成可执行,这个就是静态库的打包
通过上面的例子我们知道,需要将生成的所有目标文件和main.o文件链接才能生成可执行程序,但是除了main.o之外的.o文件都太分散了,用起来很麻烦(当然可以通过Makefile简化步骤),给别人使用也不太方便,还容易缺失,所以将它们打包。而将目标文件打包的结果就是一个静态库
使用ar指令将所有目标文件打包为静态库
ar 命令是 GNU Binutils 的一员,可以用来创建、修改静态库,也可以从静态库中提取单个模块。它可以将一个或多个指定的文件并入单个写成 ar 压缩文档格式的压缩文档文件。
例如,将Add.o和 Sub.o打包:
ar -rc libtest.a Add.o Sub.o
那么上面这个文件夹中就只有打包的库文件和头文件(因为头文件是公开的,我们通常可以看到stdio.h里面的内容)
因为自己写的库是第三方库,gcc不认识,所以需要指定-l链接我们自己写的第三方库
gcc main.c -ltest
但是链接的时候又出现错误,不能找到这个库,所以需要提供一个选项-L,说明这个库是在哪个路径底下
gcc main.c -ltest -L.
站在使用者角度:打包静态库
我们将头文件和.o文件分别放在一个文件夹下的两个文件夹中
然后将这个文件夹的压缩包给别人,用的时候就是将include里面的内容拷贝到/user/include
目录底下,将lib里面的内容拷贝到/lib64/
目录下。或者指定库的路径
指定库的路径
依然使用之前的四个文件和一个main.c文件示例。
gcc -fPIC -c Add.c
gcc -fPIC -c Print.c
位置无关代码对于 gcc 来说:
gcc -shared -o libtest.so Add.o Print.o
对于动态库,即使显式地提示 gcc main.c 中调用了第三方库中的函数,也会因为找不到库而编译错误。
例如,仍然使用 gcc 的三个选项说明编译 main.c 需要的库文件和头文件,以及应该链接哪个库。注意,此时的工作目录依然是:
gcc main.c -I./mylib/include -L./mylib/lib -ltest
sudo cp mylib/lib/libtest.so /lib64
ln -s mylib/lib/libtest.so libtest.so
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/2024_1_28/dy_test/my_lib/lib
注意要用:隔开,否则会覆盖原来的环境变量。但是这个方法是临时的,因为这个环境变量是内存级别的环境变量,机器会在下次登录时清理。