前言
在实际应用中,我们通常不想开放自己的源代码,不想让别人知道自己所写代码中的漏洞,但又需要将自己所写的代码让别人使用,这时候就需要用到库了。本章介绍库的相关知识,包括静态库与动态库的概念,静态库与动态库的制作及使用,头文件与库文件的查找与使用。
一、静态库和动态库的概念
Linux环境下:.so后缀为动态库,.a后缀为静态库。
静态库: 是在程序执行前就加入到目标程序中去了。静态库可以简单的看成一组目标文件的集合,即很多目标文件经过压缩打包后形成的文件。对于静态库,程序在编译链接时,将库的代码链接到可执行文件中,程序运行时不再需要静态库。
优点:
- 静态库在程序执行前被打包到应用程序中,因此程序加载速度快。
- 发布程序无需提供静态库,因为已经在app中。
缺点:
- 内存和磁盘空间浪费:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝
(假如一个c语言的静态库大小为1MB,系统中有100个程序需要使用到该库文件,采用静态链接的话,就要浪费进100M的内存,若数量再大,那浪费的也就更多。)
- 更新发布麻烦,不好更新。
动态库: 程序在运行时才去链接动态库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
优点:
- 链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存。
假如有程序1,程序2,和Lib.o三个文件,程序1和程序2在执行时都需要用到Lib.o文件,当运行程序1时,系统首先加载程序1,当发现需要Lib.o文件时,也同样加载到内存,再去加载程序2当发现也同样需要用到Lib.o文件时,则不需要重新加载Lib.o,只需要将程序2和Lib.o文件链接起来即可,内存中始终只存在一份Lib.o文件。
- 程序升级简单,因为app里面没有库的源代码,升级之后只要库的名字不变,函数名以及参数不变,只是实现做了优化,就能加载成功。
缺点:
- 程序加载速度比静态库慢。
- 发布程序需要提供依赖的动态库。
二、静态库的制作与使用
示例代码如下:
/*input.c*/
#include <stdio.h>
#include "input.h"
void input_int(int *a,int *b)
{
printf("input two num:");
scanf("%d %d",a,b);
}
/*input.h*/
#ifndef _INPUT_H
#define _INPUT_H
void input_int(int *a,int *b);
#endif
/*calcu.c*/
#include "calcu.h"
int calcu(int a,int b)
{
return (a+b);
}
/*calcu.c*/
ifndef _CALCU_H
#define _CALCU_H
int calcu(int a,int b);
#endif
/*Makefile*/
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o: main.c
gcc -c main.c
input.o: input.c
gcc -c input.c
calcu.o: calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
下面将input.c、calcu.c两个c文件封装成静态库文件
修改Makefile文件如下:
/*Makefile*/
main: main.c libinput.a libcalcu.a
gcc -o main main.c -L . -linput -lcalcu
libinput.a:
gcc -c input.c -o input.o
ar rcs libinput.a input.o
libcalcu.a:
gcc -c calcu.c -o calcu.o
ar rcs libcalcu.a calcu.o
clean:
rm *.o
rm main libinput.a libcalcu.a
执行make
的结果如下:
生成了libinput.a、libcalcu.a两个静态库文件。执行./main
命令,结果如下:
小结:
根据以上示例,总结一下静态库的制作与使用:
1.静态库的制作
根据Makefile文件可知制作静态库的步骤如下:
先将.c文件生成.o文件,如:gcc -c input.c -o input.o
, gcc -c calcu.c -o calcu.o
然后将.o文件打包,如:ar rcs libinput.a input.o
,ar rcs libcalcu.a calcu.o
生成了两个静态库。
2.静态库的使用
静态库制作完成后,我们完全可以移除input.c input.o calcu.c calcu.o文件,只留下头文件、main.c文件和静态库文件即可,我们将其移除之后再执行命令:gcc -o main main.c -linput -lcalcu
,如下:
出现以上错误,是因为编译器默认会去/usr/lib或者/usr/local/lib下去找静态库,当然找不到。因此需要指定静态库的目录,执行命令:gcc -o mainStatic main.c -L . -linput -lcalcu
,结果如下:
静态库实际上不常用了,下面介绍常用的动态库。
三、动态库的制作与使用
还是用上面的示例代码,修改Makefile文件如下:
main: main.c libinput.so libcalcu.so
gcc -o main main.c -L . -linput -lcalcu
libinput.so:
gcc -fPIC -shared input.c -o libinput.so
libcalcu.so:
gcc -fPIC -shared calcu.c -o libcalcu.so
clean:
rm main
rm libinput.so libcalcu.so
执行make
命令,生成libinput.so、libcalcu.so动态库。
下面我们运行程序看一下,执行命令./main
。
我们发现动态库无法加载,这就回到了静态库与动态库的区别,静态库是在程序执行前就已经加载到程序中去了,因此程序运行时无需提供静态库,而动态库是在程序运行时进行链接的。
即程序执行时无法链接到动态库,因为默认目录/usr/lib或者/usr/local/lib底下没有我们想要的动态库。倘若我们把动态库文件拷贝到/usr/lib或者/usr/local/lib下,再执行.\main
,就可以运行了。显然这种办法不合适,我们总不能将自己制作的库文件都放到系统默认目录吧,这是有风险的,也会使得该目录比较杂乱。那么针对动态库无法加载的问题,怎么解决呢?
解决办法
临时办法:
既然链接器会搜寻LD_LIBRARY_PATH所指定的目录,那么我们可以将这个环境变量设置成当前目录,执行命令export LD_LIBRARY_PATH=$(pwd)
,再次运行main程序如下:
这种方法只在当前窗口有效,重新打开一个终端仍然无法加载动态库。
当然,我们也可以写一个脚本来解决,脚本start.sh文件如下:
export LD_LIBRARY_PATH=$(pwd)
./main
赋予start.sh可执行权限并运行,结果如下:
永久办法:
在用户目录.bashrc(每次终端开户都会读取)文件中添加 export LD_LIBRARY_PATH=自定义目录
重启终端或者执行source ~/.bashrc
命令
当然还有其它办法,就不一一介绍了。
小结:
1.动态库的制作
执行命令:gcc -fPIC -shared input.c -o libinput.so
,gcc -fPIC -shared calcu.c -o libcalcu.so
,生成libinput.so、libcalcu.so两个动态库文件。
2.动态库的使用
和静态库的使用是一样的,执行命令gcc -o mainDy main.c -L . -linput -lcalcu
,生成mainDy文件,在运行mainDy程序时需要注意,与静态库不同,动态库需要在运行时指定加载位置,具体上面已经做了详细介绍。
参考文章:
Linux下gcc生成和使用静态库和动态库详解
Linux-动态链接与静态链接对比(动态库和静态库)
Linux中的库