基本概念
- 静态库(.a):程序在编译链接的时候把库的代码直接装载到可执行文件中。程序运行的时候将不再需要第三方库。打个比方:静态库如同私家车,停放的位置随意,用户可直接使用,缺点在于去哪儿都得带着这辆车,本体移动显得笨重。
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码,依赖第三方库。打个比方:动态库好比公交车,你只能去到固定的地方(公交车站台)才能使用(调用),而且可以多人共享,到达目的地后便可下车,不用去哪都带着这辆公交车,可做到本体轻量化,缺点在于一旦没有了这辆公交车,或者找不到公交站台,你什么事都办不了。
由于静态库会提前加载到进程中的代码段,如若有多个C语言程序,其c静态库的代码是冗余的,而动态库在进程调用时会加载到内存中,然后与虚拟地址空间的页表产生映射即可,所以动态库仅需一份,然后映射至多个进程即可,代码不会造成冗余。
-
ls /usr/include
查看当前系统的库文件 -
man 库文件
查看库文件的手册,如:man string.h
-
ldd 文件名
显示当前可执行文件依赖的动态库我们对该库详加查看:
发现是一个软链接,库实际上是文件,也拥有inode。
库的命名
动态库 libXXX.so
:XXX为库名
动态库 libXXX.a
:XXX为库名
所以 libc.so.6
为c库,libc++.so.6
为c++库。
安装c静态库指令
服务器可能没有静态库,这里提供c语言静态库的安装指令:
sudo yum install glibs-static
-
默认gcc编译链接的是动态库,若要链接为静态,需手动添加 -static
由于是链接静态库,ldd指令无法查看。
-
可通过
file
查看库的链接情况
静态库的打包与使用
静态库打包
我们先写两个计算加减法的函数,将其打包成静态库
add.h
#ifndef _ADD_H_
#define _ADD_H_
int add(int a,int b);
#endif
add.c
#include "add.h"
int add(int a,int b)
{
return a+b;
}
sub.h
#ifndef _SUB_H_
#define _SUB_H_
int sub(int a,int b);
#endif
sub.c
#include "sub.h"
int sub(int a,int b)
{
return a-b;
}
现在要对上面的函数接口打包成静态库:
- 首先将.c和.h进行编译获得.o文件
-c
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
- 生成静态库
-
ar -rc 静态库名 add.o sub.o
ar : gnu的归档工具,用于建立或修改备存文件,或是从备存文件中抽取文件。可集合许多文件,成为单一的备存文件。在备存文件中,所有成员文件皆保有原来的属性与权限。
- -d 删除备存文件中的成员文件
- -m 变更成员文件在备存文件中的次序
- -p 显示备存文件中的成员文件内容
- -q 将文件附加在备存文件末端
- -r 将文件插入备存文件中
- -x 自备存文件中取出成员文件
-rc : replace and creat,表示当生成静态库的.o文件发生改变时会更新静态库
-
ar -tv 静态库名
查看静态库的目录列表- t:列出静态库中的文件
- v:verbose 详细信息
调用静态库
我们将上面的静态库整理一下,.h文件放在include文件夹中,静态库放在lib文件夹中,使其看上去像个正经的静态库:
我们此时对包含了add.h和sub.h的main.c程序进行编译
程序没有找到这个静态库,因为我们自定义的静态库路径并不在程序的环境变量中,而且也不在当前目录中,所以我们要在gcc编译时,引入我们自定义的静态库的路径
-
-l (小写L)指定库名,拿数学库来说,他的库名是m,他的库文件名是libm.so。
这里我们指定的库为libmymath.a 所以库名指定
-lmymath
-
-L (L for Library)指定自定义库的搜索路径(由于自定义库的路径下可能包含多个库,所以一定要指定库名)
这里我们要连接的静态库文件的路径就是
-L./mylib/lib
-
-I (大写i for include)指定自定义头文件路径
这里我们要连接的头文件的路径就是
-I./mylib/include
总结:生成静态可执行程序(gcc -I+头文件路径-L+库路径-l+要链接的库名)
修改Makefile后重新编译,即可通过。
以上的路径为相对路径也可以是绝对路径。
我们可以在Makefile文件中,通过shell指令添加绝对路径
path=$(shell pwd)
Makefile编写如下:
path=$(shell pwd)
main-static.exe:main.c
#-I 指定头文件路径 -L 库目录路径 -l 指定库目录名称
gcc -o $@ $^ -I $(path)/mylib/include -L $(path)/mylib/lib -lmymath -static
.PHONY:clean
clean:
rm -rf main.exe
我们当然可以把头文件和库文件分别置于系统默认的头文件和库文件文件夹中:
头文件目录:/usr/include/
库文件目录:/usr/lib64
但不建议这么做,会污染命名池。
动态库的打包与使用
动态库打包
延用之前的函数库,我们将他们打包成动态库
Makefile 文件编写如下
libmymath.so:add.o sub.o
gcc -shared -o $@ $^
add.o:add.c
gcc -fPIC -c $^
sub.o:sub.c
gcc -fPIC -c $^
.PHONY:clean
clean:
rm -rf libmymath.so *.o mylibso_output
#发布,将头文件和库文件做一个打包
.PHONY:output
output:
mkdir -p mylibso_output/include
mkdir -p mylibso_output/lib
cp *.h mylibso_output/include
cp *.so mylibso_output/lib
- -shared: 表示生成共享库格式
- -fPIC:产生位置无关码(position independent code)
- 库名规则:libxxx.so
- 将头文件和库文件打包方便交付
make+make output 后得到了发布的动态库
动态库调用
将动态库复制到main.c所在文件夹下
//main.c
#include <stdio.h>
#include "add.h"
#include "sub.h"
int main()
{
printf("%d\n",add(10,20));
printf("%d\n",sub(10,20));
printf("hello world\n");
return 0;
}
我们试着延用调用静态库时的Makefile文件,gcc去掉 -static选项,并重新填入动态库的头文件,库文件以及库名。
path=$(shell pwd)
main-dynamic.exe:main.c
#-I 头文件路径 -L 库目录文件路径 -l 指定库名
gcc -o $@ $^ -I $(path)/mylibso_output/include \
-L $(path)/mylibso_output/lib \
-lmymath
.PHONY:clean
clean:
rm -rf main-dynamic.exe
可成功通过编译
但是没有办法运行
原因在于:我们只告诉了编译器动态库的位置,但是当程序运行时,操作系统并不知道链接的动态库的位置,故无法调用。
所以我们可以把自己写的动态库文件添加进系统默认的动态库搜索路径:
LD_LIBRARY_PATH
环境变量 动态库的路径
链接指令如下
现在使用ldd查看我们的可执行文件,发现已经连接上了动态库
—end—
青山不改 绿水长流