gcc——静态库与动态库应用
文章目录
1. 基础知识
关于 gcc
编译器命令详解,我会专门更新一篇博文去从头到尾讲述它的原理、用法及命令。
那本篇文章主要是针对 gcc
静态库与动态库作一番总结,网上很多 gcc
自定义库制作都不是非常的全面,所以这里我们来详细扒一扒 Linux
下 gcc
自定义库制作方法。
1.1 shell命令
那简单来提一下本章内容会用到的 gcc 编译器基本shell 命令。
-v
(–version)- gcc 版本
-Wall
- 查看警告信息
-c
- 将源文件编译为目标文件
-o
- 将目标文件链接为可执行文件
-I
/-i
- 搜索头文件目录/单个的位置
-L
/l
- 搜索库文件目录/单个的位置
1.2 文件
-
lib_funS.a
- 静态库
-
lib_funS.so
- 动态库
-
test
- 可执行文件
gcc -Wall main.c /usr/lib/lib_funS.a -o test
1.3 环境变量
文件是在 .bash_profile
当中
C_INCLUDE_PATH
- C头文件路径
CPLUS_INCLUDE_PATH
- C++头文件路径
LIBRARY_PATH
- 告诉操作系统,要到这个地方查找动态库,链接期间搜索静态库。
LD_LIBRARY_PATH
- 告诉操作系统,要到这个地方查找动态库,运行期间搜索动态库。
路径与路径之间可以加冒号来进行区分。
DIR1:DIR2:DIR3
2. 创建模板
在当前目录下创建四个文件,包含三个源文件和一个头文件,分别为 fun_a.c
、fun_b.c
、main.c
、fun.h
。
main.c
#include "fun.h"
int main()
{
fun_a();
fun_b();
return 0;
}
fun_a.c
#include "fun.h"
void fun_a(void)
{
printf("Hello Home!\n");
}
fun_b.c
#include "fun.h"
void fun_b(void)
{
printf("Hello girl!\n");
}
fun.h
#ifndef _FUN_H
#define _FUN_H
#include "stdio.h"
void fun_a(void);
void fun_b(void);
#endif
3. 静态库
静态库从创建、链接到运行,可分为三步骤。
3.1 目标文件
将需要生成静态库的所有源文件,生成目标文件(.o)。也就是将 fun_a.c
和 fun_b.c
对应生成 fun_a.o
和 fun_b.o
。
注意
不包括 main.c
文件。
gcc -Wall -c fun_a.c fun_b.c
3.2 静态库
然后我们把所有的目标文件(.o)结合生成静态库文件,取名叫做 lib_funS.a
(后缀 .a
结尾的文件)。
ar cr lib_funS.a *.o
// 等价于(二选一)
ar cr lib_funS.a fun_a.o fun_b.o
注意
-
最好我们命令为
lib_xxx.a
,后面我们链接生成执行文件将会说到。 -
我们可以通过以下命令实现查询静态库包含的文件。
ar t lib_funS.a
如图:
3.3 链接生成执行文件
生成了静态库之后,有三种方式可以实现程序的运行,那我们来仔细扒一扒能通过什么样的方式完成呢?
我们就可以通过静态库与 main.c
链接成可执行文件 (test)。
gcc -Wall main.c lib_funS.a -o test
没有任何报错,我们在来执行看下结果。
./test
注意
这里的 main.c
和 lib_funS.a` 不能够调换位置,否则会导致出错。
gcc -Wall lib_funS.a main.c -o test
如图:
扩展
普通链接
我们除了第一种链接方式,我们还可以采用最为常规的方式链接成可执行文件(test),就是把所有目标文件不通过静态库方式。
先把 main.c
生成 main.o
,因为我们在前面没有去生成。
gcc -Wall -c main.c
gcc -Wall *.o -o test
// 等价于(二选一)
gcc -Wall main.o fun_a.o fun_b.o -o test
没有任何报错,我们在来执行看下结果。
./test
缺陷链接
以上两种方法可行,第三种方法有“雷区”,需要大家注意!!!
gcc -Wall main.c -l_funS -o test
结果如图:
这种方式是有缺陷的,什么缺陷呢?gcc 编译器寻找不到 lib_funS.a
这个静态库,默认 gcc 编译器只会查找默认或指定的目录下是否存在库文件。
再这里,我们说一下,gcc 默认查找头文件区域在:
/usr/local/include
/usr/include
gcc 默认查找库文件区域在:
/usr/local/lib
/usr/lib
为了解决这个问题,我们有三种方式可以解决,从而达到 gcc 编译器能够查找到 lib_funS.a
文件,且不会报错。
方式一:
所以,我们需要把 lib_funS.a
这个静态库,移动系到其中的一个目录下,编译器才不会报错。
sudo cp lib_funS.a /usr/lib
// 等价于(二选一)
sudo cp lib_funS.a /usr/local/lib
再进行重新链接**(提醒:这里的 l_funS 就是 lib_funS 静态库的缩写。)**
gcc -Wall main.c -l_funS -o test
没有任何报错,我们在来执行看下结果。
./test
方式二:
那我们还可以通过命令 -L
查找当前静态库的位置,相对路径和绝对路径两种都可以实现链接。
gcc -Wall main.c -L. -l_funS -o test
//(二选一)
gcc -Wall main.c -L/路径 -lhello -o h3
没有任何报错,我们在来执行看下结果。
./test
方式三:
添加环境变量,LIBRARY_PATH
添加了静态库的绝对路径,也可以实现链接。
export LIBRARY_PATH=/路径:$LIBRARY_PATH
然后我们链接,发现并没有报错了。
gcc -Wall main.c -l_funS -o test
没有任何报错,我们在来执行看下结果。
./test
总结:
我们来说说这三种方式,我们应该采用哪种比较好?首先方式一直接修改系统库路径当中文件,这点一定是不推荐的,这样会导致你的系统库越来越大并且会占据更多的内存空间,而且万一手抖删掉了其它配置文件,可能会导致系统崩溃。方式三,修改环境变量,也是不推荐,因为不利于我们做移植,每个环境下都要重新配置环境变量,可知它的麻烦。。所以我们采用方式二这种方式。
3.4 运行程序
执行 ./可执行文件
,就可以运行程序了。
3.5 扩展
文件结构
我们之前说的都是所有文件在同一个目录下,那如果是一个工程项目,一定是会有若干个文件夹与文件组成。这里,我们以最基础的文件结构来说明,大家会懂得举一反三。为了方便管理,通常头文件放在 inc
文件夹当中,库文件放在 lib
文件夹当中。
main.c
#include "hello.h"
int main()
{
hello();
return 0;
}
hello.c
#include "hello.h"
void hello(void)
{
printf("Hello World!\n");
}
hello.h
#ifndef _HELLO_H
#define _HELLO_H
#include <stdio.h>
void hello(void);
#endif
过程
将所要生成库文件的源文件都生成目标文件(.o)
gcc -Wall -c -Iinc hello.c
将目标文件链接成静态库
ar cr lib_funS.a hello.o
静态库移动到 lib
文件夹当中。
mv lib_funS.a lib
到这里,我们采用方式二来完成,其它方式大家可以自己尝试。
把 main.c
编译成目标文件。
gcc -Wall -Iinc -c main.c
在将 main.o
与 静态链接库 生成可执行文件。
gcc -Wall main.o -Llib -l_funS -o test
运行可执行文件。
./test
4. 静态库与动态库区别
4.1 静态库(static libraries)
Linux下静态库以后缀 .a
命名,在windows下以后缀 .lib
命名。
4.2 动态库(shared libraries)
Linux下静态库以后缀 .so
命名,在windows下以后缀 .dll
命名,这里采用方式二 来完成,其它方式请参照以上方式。
gcc -fPIC -shared hello.c -o lib_DLL.so
gcc main.c -L. -l_DLL -o test
./test
4.3 区别
如果使用静态库,由机器码构成,将调用函数拷贝到目标文件当中,个头比较大,占的内存比较多。而动态库不是把机器码拷贝到目标文件,而是拷贝small table(内部是地址),虽增加了磁盘空间,但大大减少对内存的浪费。而且操作系统提供虚拟内存机制,使得如果有多个进程调用一个动态库,只需要留一个动态库拷贝,可以节省极大对内存浪费。
gcc 编译器默认是使用动态库。