前言:我们上一章讲了软硬连接,那么我们这次开始了解动静态库,解答什么是动态链接。
动静态库(Dynamic Static library)
1、什么是动静态库?
动静态库 ------ 即 动态库 (Dynamic Library) 与 静态库 (Static Library) 。
下面我们来分别介绍一下这两种库:
- ① 动态库 .so:程序在运行的时才去链接动态库的代码,多个程序共享使用库的代码。
- ② 静态库 .a:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
2、动态链接(Dynamic Linking)
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接 (dynamic linking) 。
动态库可以在多个程序间共享,所以 动态链接使得可执行文件更小,节省了磁盘空间。 操作系统采用虚拟内存 (VM) 机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
3、为什么需要静态库?
我们先站在设计库的工程师的角度,学如何形成静态库。
我们直接实操式地讲解,下面我们会写一段简单的、包含头文件和源文件的代码。
**代码演示:**mytest.h
cpp
#include <stdio.h>
#include <assert.h>
/* [from, to] -> 累加 -> result -> return */
extern int addToVal(int form, int to);
**代码演示:**mytest.c
cpp
#include "mytest.h"
int addToVal(int from, int to) {
assert(from <= to);
int result = 9;
int i = 0;
for (i = from; i <= to; i++) {
result += i;
}
return result;
}
**思考:**库里面需不需要 main 函数?
我们可不敢在库里带 main 函数,用户到时候会写 main 函数的,带了编译出现冲突就不好了。
所以库里面不要带 main 函数,也不需要带 main 函数。
**代码演示:**test.c
cpp
#include "mytest.h"
int main(void)
{
int from = 10;
int to = 20;
int result = addToVal(from, to);
printf("result = %d\n", result);
}
编译结果如下:
下面我们来形成一个静态库。我们先用命令行来写一下。
目前只有 2 个,不够丰富,所以我们再添加一个 myprint 功能,打印时间。
**代码演示:**myprintf.h
cpp
#include <stdio.h>
#include <time.h>
extern void Print(const char* msg);
**代码演示:**myprintf.c
cpp
#include "myprint.h"
void Print(const char* msg) {
printf("%s : %lld\n", msg, (long long)time(NULL));
}
现在我们有两组方法,一个是累加一个是打印时间,我们想把它们打包成库。
首先,我们将所有的 .c 文件翻译成 .o 文件:
cpp
1 mytest.o : mytest.c
2 gcc -c mytest.c -o mytest.o
3 myprintf.o : myprintf.c
4 gcc -c myprintf.c -o myprintf.o
5
6 .PHONY : clean
7 clean:
8 rm -f *.o
运行结果:
我们知道,链接 就是把所有的 .o 链接形成一个可执行程序。
**思考:**如果我把所有的 .o 给别人,别人能链接使用吗?可以!
只需要把程序变为 .o 就可以让被人链接用起来了,但是我们 .o 如果很多这会带来不方便。
所以我们给这些 .o 做一个 "打包",这就是静态库的作用。
4、生成静态库:ar -rc
下面我来学习如何形成静态库:
cpp
$ ar -rc [静态库] [.o]
ar 是 gnu 归档工具,rc 的意思是 replace and create (把原来打包的 .o 替换下)。
库的命名以 lib 开头,静态库以 .a 结尾,我们写进 Makefile:
cpp
libmytest.a : mytest.o myprintf.o
ar -rc libmytest.a mytest.o myprintf.o
mytest.o : mytest.c
gcc -c mytest.c -o mytest.o
myprintf.o : myprintf.c
gcc -c myprintf.c -o myprintf.o
.PHONY : clean
clean:
rm -f *.o *.a # 删除.a文件
此时我们就有了静态库,所谓了静态库就是曾经的源文件最终将它翻译成 .o 打包起来的东西而已。而别人用我们的库,就是在库里找到 .o 然后丢到而可执行程序里就行。
clean 部分我们把 *.a 加进去就行了,这样我们就可以 make clean 了:
现在,我们的 libmytest.a 就生成出来了,下面我们要做的是发布:
cpp
libmytest.a : mytest.o myprintf.o
ar -rc libmytest.a mytest.o myprintf.o
mytest.o : mytest.c
gcc -c mytest.c -o mytest.o
myprintf.o : myprintf.c
gcc -c myprintf.c -o myprintf.o
.PHONY:static
static:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include
.PHONY : clean
clean:
rm -f *.o *.a libmytest.a # 删除.a文件
结果如下:
此时就形成了静态库
5、生成动态库
动态库比静态库要复杂一些,在形成时原理跟静态库基本是一样的。
cpp
gcc -shared
区别在于 形成 .o 的时候是需要加上 gcc -fPIC 的,这是为了产生与位置无关码。
cpp
libmytest.so : mytest.o myprintf.o
gcc -shared -o libmytest.so mytest.o myprintf.o
mytest.o : mytest.c
gcc -fPIC -c mytest.c -o mytest.o
myprintf.o : myprintf.c
gcc -fPIC -c myprintf.c -o myprintf.o
.PHONY:clean
clean:
rm -f *.o *.so
结果如下:
此时我们 make 的时候就会先根据 gcc -fPIC 形成与位置无关的 .o,
然后通过 gcc -shared 的选项生成 .so 文件,此时就有了动态库。
动态库的交付:
cpp
libmytest.so : mytest.o myprintf.o
gcc -shared -o libmytest.so mytest.o myprintf.o
mytest.o : mytest.c
gcc -fPIC -c mytest.c -o mytest.o
myprintf.o : myprintf.c
gcc -fPIC -c myprintf.c -o myprintf.o
.PHONY:dyl
dyl:
mkdir -p lib-dyl/lib
mkdir -p lib-dyl/include
cp *.so lib-dyl/lib
cp *.h lib-dyl/include
.PHONY:clean
clean:
rm -f *.o *.so dyl
结果如下:
站在是一个使用者(程序员)角度-- 使用以动态库
- a.如果我们只有静态库,没办法,gcc只能针对该库进行静态链接
- b.如果动静态库同时存在,默认用的就是动态库
- c.如果动静态库同时存在,我非要使用静态库呢?
- d.我不是已经告诉了你我的动态库的路径了吗??
static的意义: 摒弃默认优先使用动态库的原则,而是直接使用静态库的方案!
6、同时生成动态库与静态库
那我们直接把两个 Makefile 合到一起看看:
cpp
.PHONY:all
all: libmytest.so libmytest.a
# 动态库
libmytest.so : mytest.o myprintf.o
gcc -shared -o libmytest.so mytest.o myprintf.o
mytest.o : mytest.c
gcc -fPIC -c mytest.c -o mytest.o
myprintf.o : myprintf.c
gcc -fPIC -c myprintf.c -o myprintf.o
# 静态库
libmytest.a : mytest.o myprintf.o
ar -rc libmytest.a mytest.o myprintf.o
mytest.o : mytest.c
gcc -c mytest.c -o mytest.o
myprint.o : myprintf.c
gcc -c myprintf.c -o myprintf.o
# 发布
.PHONY : output
output:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include
mkdir -p lib-dyl/lib
mkdir -p lib-dyl/include
cp *.so lib-dyl/lib
cp *.h lib-dyl/include
# 清理
.PHONY : clean
clean:
rm -f *.o *.a output
这样是行的,都要形成 .o,到底是位置有关还是位置无关?最终带来的结果就是不一样。
所以名字要区分开来,你生成你的,我生成我的:
运行结果:
这样,就既有动态库也有静态库了。
6、使用静态库和动态库
现在我们站在使用的人的角度,学习如何使用静态库和动态库。
代码演示:
cpp
#include "mytest.h"
#include "myprintf.h"
int main()
{
int start = 0;
int end = 0;
int result = addToVal(start, end);
printf("result: %d\n", result);
Print("Hello, World!");
return 0;
}
代码运行:gcc myfile.c
此时必然是报错的,这样头文件是找不到的。我们来回顾一下头文件的搜索路径:
① 在当前路径下查找头文件
② 在系统路径下查找头文件
我们自己写的库当前的头文件一定不在当前的系统中,你当前的头文件不在当前路径下!
它既不在当前路径,也不在头文件中,这自然是找不到头文件的。
谁在找头文件?编译器在找。系统中的头文件一般在 lib64 路径下,会存着大量的动静态库。
**第一种做法:**将自己的头文件和库文件拷贝到系统路径下即可。
gcc -l 指定我要链接的库的名称
我们还可以指定头文件搜索路径:
cpp
$ gcc myfile.c -o myfile -I ./lib-static/include/
此时链接还是失败的。
cpp
$ gcc myfile.c -o myfile -I ./lib-static/include/ -L ./lib-static/lib/ -lmytest
此时就形成了 myfile。
形成可执行程序之后,已经把需要的代码拷贝到我的代码中,运行时不依赖你的库。不需要运行时查找。
为什么动态库会有这个问题?想办法让进程找到动态库即可。
error while loading shared libraries 解决方案:
- ① 动态库拷贝到系统路径下 /lib64 安装。
- ② 通过导入环境变量的方式 ------ 程序运行的时候,会在环境变量中查找自己需要的动态库路径 ------ LD_LIBRARY_PATH。
- ③ 系统配置文件来做。