动态库静态库生成与应用,详细讲解与制作
摘要:在做项目的过程中,我们不需要再对已经做过的功能再次编写,可以直接拿来用。或者说多团队协作开发项目的时候,团队A只需要团队B所提供的功能,而不需要知道团队B如何实现的,类似面向对象的意思。团队B所写的代码就是制作库的过程,而团队A就是消费库的角色。
本文主要讲解下linux下,运用gcc来制作静态库和动态库。
1.静态库与动态库概念
1.静态库概念
我们将某些功能封装成库,供用户静态调用的,这就是静态库。linux下的命名规则为:
libxxx.a
lib
为静态库的开头,xxx
为库的名字,并且以 .a
结尾
为什么叫静态库呢,其实就是用户静态调用,也就是直接调用的意思。我们在代码编译的时候,直接将静态库编译到我们的程序中,直接调用,这就是静态库。
2.动态库概念
我们将某些功能封装成库,供用户动态调用,这就是动态库,linux下的命名规则为:
libxxx.so
lib
为动态库的开头,xxx
为库的名字,并且以.so
结尾。和静态库的差别是结尾。
为什么叫动态库呢,其实就是用户动态调用的意思,我们在编译的时候不会加载动态库,而是在运行的时候才会去加载库,这就叫动态库,也叫共享库。
不管是静态库还是动态库,其实就是我们调用程序的一种方式,是在编译的时候就加载,还是在运行的时候加载。
2.库的制作与应用
这里举个例子来说明怎么制作和应用静态库以及动态库
首先我们写了3个.c文件来分别表示3种功能,
长方形的周长
//rectangle_circu.c
#include "head.h"
int rectangle_circu(int length, int width)
{
return (length * 2 + width * 2);
}
三角形的周长
//triangle_circu.c
#include "head.h"
int triangle_circu(int a, int b, int c)
{
return (a + b + c);
}
圆形的周长。
//circle_circu.c
#include "head.h"
int circle_circu(int radius)
{
return (2 * 3.14 * radius);
}
h文件
//head.h
#ifndef __HEAD_H__
#define __HEAD_H__
int triangle_circu(int a, int b, int c);
int rectangle_circu(int length, int width);
int circle_circu(int radius);
#endif
我在制作的过程中,是分别将这4个文件放在不同的2个文件夹下面的。如下图
1.静态库的制作与应用
在制作静态库之前需要了解gcc,这里就默认都了解gcc了。
1. 制作静态库的命令
在src文件夹下生产.o文件。先cd
到src文件夹下。
gcc -I ../include *.c -c
这里解释下命令: -I
表示包含的头文件路径。-c
表示生产.o
文件。
整个命令的意思是,在上一个目录下的include文件夹下去寻找.h
文件,并将本路径下的所有.c
文件生成对应的.o
文件
执行了这个命令后,就会在src文件下多出几个.o
文件
有了.o文件后,就可以对.o文件进行打包生成我们的静态库了。
2. 打包静态库
这里用到了打包用的命令是ar
.
我们进入到src目录下
ar rcs libMycircu.a *.o
使用这条命令就可以生成我们的静态库libMycircu.a
了。
查看src目录下,会多一个文件
为了方便应用与发布静态库,我们将库移动放到特定的专门的lib文件夹下面。
移动到和src同级的lib文件夹下面。
3.静态库的应用
我们写个函数来调用这个静态库
这里写一个main.c来调用我们静态库里面的内容。
//main.c
#include <stdio.h>
#include "head.h"
int main()
{
int length = 5;
int width = 4;
//长方形的周长
int len = rectangle_circu(length, width);
printf("长方形的周长为:%d\n", len);
return 0;
}
写好调用函数以后,我们就来编译这个main.c,
这里可以有2中方式来编译,
第一种方法
用到的命令如下
gcc main.c -I include lib/libMycircu.a -o circu
在当前目录,生成了可执行文件circu
,
我们来执行下
./circu
执行结果:
我们看到这是我们想要的结果。
静态库调用成功。
第二种方法
用到的命令如下:
gcc main.c -I include/ -L lib/ -l Mycircu -o circu
命令解析:
-I
表示包含的头文件, -L
表示静态库的位置,-l
表示库的名字,注意库的名字(libMycircu.a
)是去掉了头(lib
)和尾(.a
)的。
同样我们可以得到可执行文件,如第一种方法一样,执行结果也一样。这里就不在赘述。
2.动态库的制作与应用
制作动态库的步骤
1.生成和位置无关的代码
什么叫和位置无关的代码,其实就是说在加载的时候是以绝对地址,还是相对地址来加载的。我们静态库是在编译的时候就加载到程序中的,用的是绝对地址。而我们动态库是在程序运行的时候才加载的,用的就是相对地址。
怎么生成和位置无关的代码呢。
其实也是生成.o
。只是和静态库的方法不一样。
我们先cd到src目录下。
用的命令如下:
gcc -fPIC -c *.c -I ../include/
执行这条命令以后,就会生成相应的.o
文件,
2.打包
这里打包用的命令如下:
gcc -shared -o libMycircu.so *.o -I ../include/
用这个命令就打包了我们的动态库libMycricu.so
。
为了方便应用与发布动态库,我们将库移动放到特定的专门的lib文件夹下面。
移动到和src同级的lib文件夹下面。
3.如何使用动态库
同样这里可以有2中方式来编译,
第一种方法
用到的命令如下
gcc main.c lib/libMycircu.so -o circu -I include/
在当前目录,生成了可执行文件circu
,
我们来执行下
./circu
执行结果:
第二种方法
用到的命令如下:
gcc main.c -I include/ -L lib/ -l Mycircu -o circu
命令解析:
-I
表示包含的头文件, -L
表示静态库的位置,-l
表示库的名字,注意库的名字(libMycircu.so
)是去掉了头(lib
)和尾(.so
)的。
执行生产的可执行文件Mycircu:
我们发现可执行文件出错了!!
这是为什么呢?
我们用ldd
命令来查看生产的可执行文件需要连接的动态库,
发现问题了,原来是可执行文件链接不到我们自己制作的动态库!!
怎么才能链接到自己的动态库呢。
这里提供两种方法。
1.可以将我们自己制作的库临时加载到环境变量中;
系统为动态库提供了一个环境变量:LD_LIBRARY_PATH
.
LD_LIBRARY_PATH
主要的作用就是指定查找的动态库。
用如下命令指定动态库:
export LD_LIBRARY_PATH=~/hellomyself/lib_test/lib/libMycircu.so
运行上面的指令后,我们可以用ldd来查看可执行文件的库连接。
链接成功啦!
可以执行程序了:
这样加载只在当次有效,如果关掉终端就又得手动把动态库加载到环境变量中。
2.将我们制作的库,永久的加到系统配置文件中。
linux系统中提供了一个专门为动态库加载的配置文件,位置在/etc/ld.so.conf
打开这个文件,
sudo vi /etc/ld.so.conf`
然后在末尾加上我们自己的动态库的路径,
如下图:
然后更新这个文件:
sudo ldconfig -v
再次查看可执行文件的链接库:
我们自己的库已经链接成功啦!
这些可以执行可执行文件了:
执行结果符合预期,并且和静态库一致。
功成!
3.动态库与静态库的优缺点
1.静态库
优点:
1.由于静态库是在编译的时候就加载到可执行程序里面的,所以发布的时候不需要提供静态库;
2.由于静态库是在编译的时候就加载到可执行程序里面的,执行比较快。
缺点:
1.如果静态库修改了,就得重新编译可执行程序;
2.体积比较大。
1.动态库
优点:
1.由于动态库是执行的时候才去加载,所以可执行程序,体积小;
2.当动态库更新后(接口不变的更新),不用重新编译可执行程序,直接替换动态库即可。
缺点:
1.发布可执行程序的时候,需要将动态库一并给到用户;
2.由于程序是动态加载的,所以会相对静态库来说,加载会相对教慢。
这是简单的在linux下制作静态库和动态库的过程,已经优缺点。当然这里面还设置到很多程序内存分布的知识,后面可以专门拿来分析。