从开始学C语言写第一个"hello world"历程到现在,我依然困惑于到底这个程序完整的执行流程是什么样的。不过,现在我正在尝试一点一点的揭开它的面纱。现在,我尝试分析linux中C语言静态库和动态库生成和调用的方法,这可以算作实现最终愿望的一小步。
首先说明的是,本文参考于linux 静态库、共享库,这篇文章写的的确不错。笔者结合自己的学习过程,稍作修改。
一、什么是库
本质上说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。由于windows和linux本质的不同,因此二者的二进制库是不兼容的。linux操作系统支持的函数库可分为静态库和动态库,动态库又称为共享库。linux系统有几个重要的目录存放着相应的函数库,如/lib、/usr/lib。
二、静态库和动态库
A.静态库
这类库的名字一般是libxxx.a,利用静态库编译生成的可执行文件比较大,因为整个函数库的所有数据都被整合进了可执行文件中。
它的优点就是程序执行时不需要外部的函数库支持,它的装载速度要比动态库快。它的缺点因为它的优点而产生,静态库升级后,那么你的程序就必须重新编译。当多个应用程序都用到相同的库中,执行这些程序时,会把静态库重复调入内存,而造成内存的浪费。
B.动态库
这类库的名字一般是libxxx.so,动态库又称共享库。相对于静态函数库,动态函数库在编译的时候没有被编译进可执行文件中,所以动态函数看所生成的可执行文件比较小。程序执行过程中,需要动态申请并调用相应的库才能运行,所以程序的运行环境中必须提供相应的库。
它的优点是多个应用程序可以使用一个共享的动态库,启动多个应用程序的时候,只需要动态库加载到内存一次即可,自然比较节省内存。它的缺点是启动加载时候速度比较慢。
三、函数库的创建
实验的所有文件及内容如下:
add.c
#include "heads.h"
int add(int a,int b)
{
return a+b;
} sub.c
#include "heads.h"
int sub(int a,int b)
{
return a-b;
} heads.h
#ifndef _HEADS_H_
#define _HEADS_H_
extern int add(int,int);
extern int sub(int,int);
#endif test.c
#include <stdio.h>
#include "heads.h"
int main()
{
int a=5,b=6;
printf("a+b=%d\n",add(a,b));
printf("a-b=%d\n",sub(a,b));
return 0;
}
注意:不管是静态库还是动态库,原材料都是目标文件(*.o),所以先将源文件做成目标文件。
A.静态库的创建
ar -cr libaddsub.a add.o sub.o
ar 静态函数库创建命令
-c 不管库是否存在,都将创建
-r 在库中插入模块
B.动态库的创建
gcc -shared -pic -o libaddsub.so add.o sub.o
-shared 生成共享库
-pic 产生位置无关代码
四、函数库的使用
A.静态库的使用
gcc test.c -static -L. -laddsub -o test1
static 使用静态库连接
-llibrary 用lib和.a把library包裹起来,并且在系统的标准搜索目录和L指定的目录中搜素该库文件
-Ldir 在l选项搜素目录中添加dir目录
可以看到程序编译通过并可以执行
当删除了静态库文件libaddsub.a,发现程序依然能够执行,也就是说使用静态库连接生成的程序执行时不依赖于静态库
B.动态库的使用
gcc test.c -L. -laddsub -o test2
通过这个命令可以看见仅仅是去掉了static选项。这种情况,gcc优选选择连接动态库,当没有合适动态库的动态库连接时才连接静态库。下边的实验做之前,在/work目录下已经生成了动态库libaddsub.so。
可以看到生成的test2明显比test1小很多,即证明了动态库连接可以使可执行文件比较小
但是,直接运行却提示找不到库,这是因为动态库libaddsub.so所在目录/work并没有添加到执行时搜索动态库的目录中去。
五、动态库的运行
(1) 第一种解决的办法是使用环境变量LD_LIBRARY_PATH,这个环境变量就可以指定执行时搜索动态库的目录,这里把/work目录添加进去。
(2)把动态库libaddsub.so拷贝到/lib或者/usr/lib中去,这种办法将在第七部分实验
(3)修改配置文件
#cd /etc/ld.so.conf.d 进入配置文件夹
#vi mylib.conf 建立一个配置文件*.conf,这里以mylib.conf为例,并添加动态库所在目录,这里是/work
#ldconfig 使配置生效,实际上是更新ld.so.cache文件
实验过程及结果如下:
再查看test2所有依赖的动态库
可以看到,test2依赖的动态库有三个,包含libaddsub.o和两个系统标准的动态库。可执行文件test2的大小为12037Bytes。
当删除动态库libaddsub.so后,再来运行test2,不能执行,说明动态库在运行过程中还需要动态库libaddsub.so。
六、查看库
nm 可以打印出动态库或者静态库中涉及到的所有符号
ldd 可以查看一个可执行程序依赖的共享库
七、连接和运行选项
A.将静态库libaddsub.a移动到系统标准库目录中测试编译
编译可以通过,执行也可以通过,说明系统标准库目录中的静态库可以被连接。另外,注意这时的test1大小是423629Bytes
将static去掉重现编译,发现test1大小变成了11817Bytes,莫非此时libaddsub.a是被动态连接上的?
但是,将libaddsub.a删除之后,重新运行test1,仍然可以执行,可以得知libaddsub.a是被静态连接的。查看,test1的构造,可以看到它依赖于两个标准动态库。说明,没有static连接器选项时,默认找的是动态库,但是没有动态库时就连接静态库。
test1中libaddsub.a是被动态连接,而另外两个标准库是被动态连接的。
B.用类似的方法分析动态库
可以看到系统标准库目录中的动态库可以被连接,而且在执行时可以被搜索到。
从第五部分可以知道,使用环境变量LD_LIBRARY_PATH可以指定执行时搜索动态库的目录,那么它是否能指定连接器的搜索目录呢?
从上边的实验,可以看出环境变量LD_LIBRARY_PATH不能指定连接器的搜索目录。
参考博客:linux 静态库、共享库