1. 概念
- 动态库:程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码
- 静态库:程序在编译链接的时候把库的代码拷贝到可执行文件中,拷贝到可执行程序的代码 区,必须通过相对确定的地址位置来访问。程序运行的时候将不再需要静态库
-
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文 件的整个机器码
-
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间
静态库 | 动态库 | |
Linux | .a | .so |
Windows | .lib | .dll |
2. 库原理
//my_add.h
#pragma once
#include <stdio.h>
extern int Add(int a, int b);
//my_add.c//
#include "my_add.h"
int Add(int a, int b)
{
printf("enter Add fun, %d + %d = ?\n", a, b);
return a + b;
}
///my_sub.h///
#pragma once
#include <stdio.h>
extern int Add(int a, int b);
///my_sub.c///
#include "my_add.h"
int Add(int a, int b)
{
printf("enter Add fun, %d + %d = ?\n", a, b);
return a + b;
}
//main.c///
#include "my_add.h"
#include "my_sub.h"
int main()
{
int a = 10;
int b = 20;
int ret = Sub(a, b);
printf("result is %d\n", ret);
ret = Add(a, b);
printf("result is %d\n", ret);
return 0;
}
首先我们再当前的目录创建上面的四个文件,主函数main.c在test目录下,执行:
gcc -c my_sub.c -o my_sub.o
gcc -c my_add.c -o my_add.o
两条命令生成.o文件(可重定位目标二进制文件)
那么我们如何使用.o文件呢?进入test目录,将上层的.h和.o全部拷贝到test目录中
再生成main.c的.o文件
最后执行gcc -o mymath *.o,生成可执行文件mymath
从上面的实验可以得知,当其他人的程序调用我们自己构建的函数时,不是一定需要源代码才能使得其程序运行起来,只需要提供.o文件和头文件进行链接即可。
那么一些人就想到,将所有的".o"文件打一个包,给对方提供一个库文件即可,又跟库这些打包方式的不同有了动/静态库。
库的本质就是.o文件的集合
3. 静态库
首先在目录下创建Makefile:将my_add.o my_sub.o打包到libmymath.a中
Makefile//
libmymath.a:my_add.o my_sub.o
ar -rc $@ $^ # 将my_add.o my_sub.o打包到libmymath.a中
my_add.o:my_add.c
gcc -c my_add.c -o my_add.o
my_sub.o:my_sub.c
gcc -c my_sub.c -o my_sub.o
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp -f *.a mylib/lib
cp -f *.h mylib/include
.PHONY:clean
clean:
rm -rf *.o libmymath.a mylib
执行make和make output,得到了mylib以及所有需要的文件再执行: tar czf mylib.tgz mylib 命令,那么我们的库就可以供别人提取下载使用了
将mylib.tgz拷贝到test目录下解压,使test下只有main和库文件,在这里模拟使用库文件的用户:
此时我们的main中用到了库文件的函数,我们直接编译,提示gcc找不到头文件
我们给gcc指定找头文件的位置:gcc -o mymath main.c -I ./mylib/include 这里的-I就是指定找头文件的路径
此时有提示我们发生错误,找不到库文件,哈需要告诉编译器在哪里找库文件:gcc -o mymath main.c -I ./mylib/include -L ./mylib/lib 这里的-L就为查找库文件的路径
还是报同样的错误,是因为我们要链接库时,必须指定要链接的库名称,再gcc命令后加 -l 库名,这里是小写的L 。
那为什么再以前自己再写代码时从来没有指定库名称?--是因为gcc和g++默认给指定了。注意这里的库名称为去掉前后缀的名称。这样程序就可以正常的执行了
上图看出为什么程序是动态连接的,形成一个可执行程序不只依赖于一个库文件,可能其他的库文件是动态链接的,而且gcc、g++默认是动态链接的,只要有一个是动态连接的file时就是动态链接。对于一个指定的具体的库,究竟是动/静,取决于你提供的是动态库还是静态库。
在上面我们对静态库的使用时,好像步骤显得略微的繁琐,每次使用时很不方便,我们可以把我们的库安装到系统库文件目录下。
sudo cp mylib/include/* /usr/include
sudo cp mylib/lib/*.a /lib64
执行上面两行代码后,只需要指定库的名称即可编译运行了:
4.动态库
静态库在生成.o文件时,需要加一个选项 "-fPIC" ,将.o打包为库文件的命令是:
gcc -shared -o libmymath.so *.o 注意:动态库为.so文件
gcc -o mymath main.c -I ./mylib/include/ -L ./mylib/lib/ -l mymath,在执行完这段代码以后,程序还是不能执行,明明把库文件和头文件路径都告诉了gcc,为什么还是不能执行呢?因为当我们把程序编译完时,运行于gcc就没有关系了,程序要想运行起来,OS和shell也是需要库在哪里的!而库并不在系统路径下,OS无法找到动态库。
那么如何解决上面的问题呢?-- OS除了在默认库路径下找,还会在环境变量中去找。
查看环境变量的命令:echo $LD_LIBRARY_PATH
- 可以把库路径添加到环境变量中 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/shuaishuai/106/106-code-blackboard-writing/lesson22/test/mylib/lib
此时就可以连接上了,并可以正确的运行
- 还可以将动态库所在的路径拷贝到/etc/ld.so.conf.d/路径下的配置文件中,再更新缓存sudo ldconfig 即可
- 还可以在test目录下创建一个动态库的软链接,或者将软连接建立到系统的库目录下