vlambda博客
学习文章列表

C语言 | 什么是静态链接库和动态链接库?

今天分享的是静态链接库和动态链接库的相关知识,并且自己动手写一个简单的静态函数库和动态函数库,来体验这个流程。
首先要从函数库的发明开始说起。
我们在写C语言代码的时候,一般都是先写
#include <stdio.h>
这是一个标准输入输出的头文件,因为我们可能要用到像printf这类的函数,而这类函数就是包含在这个头文件当中。但是为什么包含这个头文件就可以使用里面的函数呢?
早期,程序员写代码都是从0开始写的,后来慢慢的发现,有些功能的函数会被反复使用到,如果大家写代码都要从头开始写就非常浪费时间,于是程序员们会经常在一起交流,看一下大家写了什么好代码,可以相互借鉴,把那些通用的代码保留起来,下次就可以直接用了。
刚开始是直接交换源文件的形式,就是我把我写好的.c文件给你,你把你写好的.c文件给我,这样的交换非常彻底,因为我可以通过源文件知道你函数具体是怎么实现的。但是这种方式有它的缺点, 缺点就是无法以商业化形式来发布函数库。
商业公司需要将自己的有用的函数库共享给别人(当然是付费的),但是又不能给客户源代码。这时候的解决方案就是以库(主要有2种:静态库和动态库)的形式来提供。
比较早出现的是静态链接库。静态库其实就是商业公司将自己的函数库源代码经过 只编译不链接形成.o的目标文件,然后用ar工具将.o文件归档成.a的归档文件(.a的归档文件又叫静态链接库文件)。商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用。动态链接库比静态链接库出现的晚一些,效率更高一些,是改进型的。现在我们一般都是使用动态库。
静态库与动态库的区别就是,静态库在用户链接自己的可执行程序时就已经把调用的库中的函数的代码段链接进最终可执行程序中了,相当于是把别人的代码拷贝了一份,哪里有调用,哪里就要拷贝,这样程序是很完整的,但是有个缺点,就是非常占内存,如果反复调用一个函数,就要拷贝很多份相同的代码。
而动态库不一样,动态链接库本身不将库函数的代码段链接入可执行程序,只是做个标记。然后当应用程序在内存中执行时, 运行时环境发现它调用了一个动态库中的库函数时,会去加载这个动态库到内存中,然后以后不管有多少个应用程序去调用这个库中的函数都会跳转到第一次加载的地方去执行(不会重复加载)。
gcc中编译链接程序默认是使用动态库的,要想静态链接需要显式用
-static
来强制静态链接。

上面讲的都是链接库的背景知识,接下来自己制作一个简单的链接库并且使用。


  1. 制作静态链接库

首先准备我们 源文件 和头文件,因为只是简单示例,所以代码不需要写太复杂。
//源文件#include <stdio.h>#include "test.h"
int add(int a,int b){  return a+b;}
//头文件#ifndef __test_H#define __test_H
int add(int a,int b);
#endif

接下来使用gcc -c只编译不连接,生成.o文件;然后使用ar工具进行打包成.a归档文件库名不能随便乱起,一般是lib+库名称,后缀名是.a表示是一个归档文件。这里我们通过Makefile来实现。
all: gcc main.c -o main -ltest -L.
lib: gcc -o test.o test.c -c ar -rc libtest.a test.o
clean: rm maincleanall: rm main rm *.o *.a
这里的lib目标里面的两行代码就是生成静态库的代码。所以我们可以使用
make lib
命令来生成静态库。

C语言 | 什么是静态链接库和动态链接库?

第一句由test.c生成test.o,第二句由test.o生成libtest.a文件。这个.a文件就是静态库文件,lib后面的是库文件名称。 制作出来了静态库之后,发布时只需要发布.a文件和.h文件。以上就是静态链接库的制作过程。

假设我们已经拿到了别人发布的.a和.h文件,那么要怎么使用呢?这里先写一个测试代码:
#include <stdio.h>#include "test.h"int main(void){ int a; a=add(3,8); printf("a=%d\n",a); return 0;}
首先肯定要在测试代码中包含相应的头文件才能调用函数。正常来讲,上面的代码应该可以直接编译运行了,我们来试一下
gcc main.c

C语言 | 什么是静态链接库和动态链接库?

结果发现会报链接错误。这是因为编译器只会去链接几个常用的库,如果要链接其他的库,必须使用-l指令。所以我们再试一次
gcc main.c -ltest

C语言 | 什么是静态链接库和动态链接库?

这个时候还是有问题,提示找不到,这是因为这个库是我们自己写的,编译器并不知道去哪里找,所以,还应该指定库的路径
gcc main.c -o jintai -ltest -L. -static
-L. 就是指定链接到当前路径,这个时候我们发现已经可以正常编译程序,并且可以生成可执行文件,执行结果也是正确的。

C语言 | 什么是静态链接库和动态链接库?

以上就是静态链接库的制作及使用方法。


ii.制作动态链接库

制作动态链接库的编译指令要稍作修改

lib:  gcc test.c -o test.o -c -fPIC gcc -o libaston.so test.o -shared 
-fPIC是位置无关码,-shared是按照共享库的方式来链接。
编译完之后可以得到一个.so文件,这个文件就是动态链接库文件。做库的人给用库的人发布库时,发布libxxx.so和xxx.h即可。
对于动态链接库,该怎么使用呢?
我们直接使用
gcc main.c -o dongtai
会发现报错,错误和静态差不多。经过测试会发现,也必须使用
gcc main.c -o dongtai -ltestso -L.
指令编译才成功,也就是说要指定链接库和这个库的路径。
但是我们一执行还是发现会报错

C语言 | 什么是静态链接库和动态链接库?

./dongtai: error while loading shared libraries: libtestso.so: cannot open shared object file: No such file or directory
这是因为动态链接库运行时需要被加载(运行时环境在执行程序的时候发现他动态链接了libtestso.so,于是乎会去固定目录尝试加载libaston.so,如果加载失败则会打印以上错误信息。)
这就是动态和静态的一个区别,静态库在编译完没问题之后就可以直接运行,因为他生成的可执行文件是完整的,但是动态库不行,他必须要去固定的目录(其实就是环境变量)加载内容到内存中。
该怎么解决呢?两种常用方法:
1、将libtestso.so放到固定目录下就可以了,这个固定目录一般是/usr/lib目录。
 sudo cp libtestso.so /usr/lib
复制过去之后就可以正常执行了。

2、使用环境变量LD_LIBRARY_PATH。操作系统在加载固定目录/usr/lib之前,会先去LD_LIBRARY_PATH这个环境变量所指定的目录下去寻找,如果找到就不用去/usr/lib下面找了,如果没找到再去/usr/lib下面找。所以解决方案就是将libaston.so所在的目录导出到环境变量LD_LIBRARY_PATH中即可。
 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/d/test
冒号后面的是 libaston.so所在的路径。通过这个方法也可以正常执行。


以上就是今天的内容,主要是介绍了函数库的来源和背景知识,以及静态链接库和动态链接库的制作及使用方法。