动静态库的原理
我们知道可执行文件前的4步骤 预编译->编译->汇编->链接
- 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
- 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
- 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
- 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。
例如,我们上面用5个文件组合出了可执行文件。.0文件就是汇编后的文件。
我们在最后一步的时候不链接,而是把这些.o文件打包,
这样的一堆.o文件组合到一起就是一个库了。
认识动静态库
#include <stdio.h>int main()
{printf("hello world\n"); //库函数return 0;
}
上述代码我们用了一个库函数printf。
生成后,我们用ldd查看一个可执行程序依赖的库文件。
libc.so.6就是依赖的文件。
我们找到上面的文件目录,我们看到我们上面的连接其实就是一个软连接。
我们可以查查看这个库文件
我们可以看到他是一个shared object。
shared object说明这就是一个共享的目标文件库,准确来说,还是一个动态库
- linux中 以.so为后缀的是动态库,以.a为后缀的是静态库
- Windows中,以.dll为后缀的是动态库,以.lib为后缀的是静态库。
这里可执行程序锁依赖的.so文件就是动态库,gcc/g++默认的是动态链接,若想进行静态链接,可以加一个-static选项。
gcc -o mytest-s mytest.c -static
可以看到我们静态库 的大小远远大于可执行文件。
我们查看二者的情况,一个是dynamically 动态链接 一个是 statically 静态链接。
动静态库各自的特征
静态库
静态库是在编译链接时期,直接把库的代码复制一份到可执行文件中,生成的可执行程序在运行的时候不再需要静态库,所以里面多了一个库的代码所以肯定会大。
优点:
静态库,只要生成可执行程序后,这个程序就可以独立运行了,静态库就没用了
缺点:
由上面也可以看出静态库生成的代码会占用大量的空间,并且如果这个库被多个程序使用,就会导致每个程序都多一个静态库代码。代码还都是重复的
动态库
动态库:就是在程序运行时,在共享区中找到对应的动态库,此时去动态库中调用代码。
动态库就相当于,一开始就在物理内存加载好一份,然后程序调用对应代码时,通过虚拟内存和物理内存映射的方式,找到对应的函数。
优点:
节省空间,多个库调用的都是在物理内存中的同一个库,即同一个代码。
缺点:
必须依赖动态库,否则无法运行。
静态库的打包与使用
我们写4个文件,add.h和add.c和sub.h和sub.c都是很简单的代码
就是一个头文件,一个原文件,里面就一个加减函数。头文件声明这个函数。
1.生成.o文件。
生成两个.o文件 gcc -c选项。
2.使用ar命令将所有目标文件打包为静态库
ar指令常用于将目标文件打包为静态库
-r: 若静态库文件中的目标文件有更新,则用新的目标文件替换为旧的目标文件(replace)
-c:建立静态库文件(create)
ar -rc libcal.a add.o sub.o
-t:列出静态库的文件
-v:显示详细信息。
3.把头文件和生成的静态库组织起来
当我们把自己的库给别人用的时候,可以看到上面我们有一个静态库文件和剩下的.h的文件,所以我们就是要把头文件和.o的静态库文件给到别人就可以。
makefile文件:
mylib=libcal.a
CC=gcc$(mylib) : add.o sub.oar -rc -o $(mylib) $^
%.o:%.c$(CC) -c $<.PHONY:clean
clean:rm -f $(mylib) ./*.o.PHONY:output
output:mkdir -p mathlib/includemkdir -p mathlib/libcp ./*.h mathlib/includecp ./*.a mathlib/lib
make编译文件,makeout将文件放入mathlib文件夹中。
此时就是把mathlib给到别人,此时别人有静态库和头文件.h,只差最后一步链接即可使用。
使用静态库
创建一个源文件main.c,然后使用库函数。
#include <stdio.h>
#include <add.h>int main()
{int x = 20;int y = 10;int z = my_add(x, y);printf("%d + %d = %d\n", x, y, z);return 0;
}
此时我们的文件夹里只有两个文件,main.c和我们打包的静态库
方法一:使用选项
此时使用gcc编译main.c文件生成可执行程序就需要携带三个选项了
- -I : 指定头文件的搜索路径
- -L:指定库文件的搜索路径
- -l:指明需要链接库文件路径下的哪一个库
指定库代码如下:
gcc main.c -I ./mathlib/include/ -L ./mathlib/lib/ -lcal
结果如下:
为什么要指定搜索文件?
我们知道操作系统中有环境变量这个说法,所以我们在使用一些系统的库的时候,没有指定路径,而是直接使用,而我们自己定义的库没在环境变量下,此时使用就得带上指定的路径,让系统去找。当然也可以把这个路径放到环境变量下。
1.指定头文件的路径,是找到头文件->找到声明。
2.指定库的路径,是找到库->找到定义。
3.因为我们库可能不只是一个,所以我们要指定需要找到哪个库文件,所以-lcal就是指定名字。
4.上述三个选项后可以加空格也可以不加。
方法二:把头文件和库文件塞到对应的系统路径下
就是进入环境变量的意思,放到系统路径后就能让操作系统自动找到对应库了。
将库文件拷贝进系统路径下。
sudo cp mathlib/lib/libcal.a /lib64/
sudo cp mathlib/include/* /usr/include/
结果如下:
我们发现我们把文件放到系统路径后,只用指定库的名字就完成了调用。
实际我们把头文件和库文件塞到系统路径就是安装库的过程,但不推荐将自己写的头文件和库文件拷贝到系统路径下,这样其实是造成污染的。
动态库的打包与使用
打包
动态库和静态库都是库,但是二者在打包时有一点点区别。我们还是使用上述的4个文件进行打包
1.还是让源文件生成.o文件
这里生成文件时,需要多加一步操作:
带上选项 -fPIC(position independent code):产生位置无关码。
gcc -fPIC -c add.c sub.c
选项说明:
-c 生成.o文件。
-fPIC :生成位置无关码。告诉编译器产生位置无关码,给动态库使用
2.使用-shared选项将所有目标文件打包为动态库
和静态库ar 指令不同,动态库的生成用 gcc 带 -shared选项。
gcc -shared -o libcal.so add.o sub.o
我们发现此时生成的动态库的大小 << 静态库的大小。
3.将头文件和生成的静态库组织到一个文件夹下面
使用makefile把文件组织起来
原始步骤就是
我们用makefile将指令操作汇总一下:
mylib=libcal.so%.o:%.cgcc -fPIC -c $<
$(mylib):add.o sub.ogcc -shared -o $(mylib) $^.PHONY:clean
clean:rm -rf $(mylib) ./*.o.PHONY:output
output:mkdir -p mlib/includemkdir -p mlib/libcp ./*.h mlib/includecp ./*.so mlib/lib
注意:makefile的缩进要用tab键,用空格键可能会出错。
结果如下:
此时继续使用main.c测试整个库代码。
#include <stdio.h>
#include <add.h>int main()
{int x = 20;int y = 10;int z = my_add(x, y);printf("%d + %d = %d\n", x, y, z);return 0;
}
gcc main.c -I./mlib/include -L./mlib/lib -lcal
使用指令后,生成可执行文件。
我们发现文件不能执行。
我们发现我们的动态库文件是not found的。
方法一:拷贝.so文件到系统共享路径下
这里就是让你把.so拷贝到和 c库文件同一个目录下,此时操作系统就会自动找到库了。
sudo cp ./libcal.so /lib/x86_64-linux-gnu/
方法二:更改LD_LIBRARY_PATH
LD_LIBRARY_PATH是一个环境变量,这个环境变量是程序运行时要搜索的路径。所以把动态库所在的目录添加到LD_LIBRARY_PATH即可。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/~/blog-code/DSL-D
我们看到.so上面也有一串地址,说明当前库是找到了。
方法三:配置/etc/ld.so.conf.d/
这个目录下面放的都是以.conf为后缀的配置文件,这些配置文件就是一堆路径,和环境变量一样,系统会自动进行查找。
echo ~/blog-code/DSL-D/mlib/lib > aron.conf
sudo cp aron.conf /etc/ld.so.conf.d/
上述两个命令:第一个是将上面的地址打印到aron.conf中
第二个是把aron.conf放到上述目录中。
sudo ldconfig
使用上述指令刷新配置文件。
此时执行文件