本节就如何创建和使用程序库进行论述。所谓” 程序库”,简单说,就 是包含了数据和执行码的文件。其不能单独执行,可以作为其它执行程序的一部分来完成某些功能。库的存在,可以使得程序模块化,可以加快程序的再编译,可以实现代码重用,可以使得程序便于升级。程序库可分静态库 (static library) 和共享库 (shared object)。
静态库
静态库是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。静态库可以认为是一些目标代码的集合。按照习惯,一般以”.a” 做为文件后缀名。使用 ar(archiver) 命令可以创建静态库。因为共享库有着更大的优势,静态库已经不经常使用。但静态库使用简单,仍有使用的余地,并会一直存在。有些 Unix 系统,如 Solaris 10,已经基本废弃了静态库。
静态库在应用程序生成时,可以不必再编译,节省再编译时间。但在编译器越来越快的今天,这一点似乎已不重要。如果其他开发人员要使用你的程序,而你又不想给其源码,提供静态库是一种选择。从理论上讲,应用程序使用了静态库,要比使用动态加载库速度快 1-5%,但实际上可能并非如此。由此看来,除了使用方便外,静态库可能并非一种好的选择。
要创建一个静态库,或要将目标代码加入到已经存在的静态库中,可以 使用以下命令:** 静态库
ar rcs libmylib.a file1.o file2.o
以上表示要把目标码 file1.o 和 file2.o 加入到静态库 libmylib.a 中 (ar 的参数 r)。若 libmylib.a 不存在,会自动创建 (ar 的参数 c)。然后更新.a 文件的索引,使之包含新加入的.o 文件的内容 (ar 的参数 s)。
静态库创建成功后,需要链接到应用程序中使用。使用 gcc 的 -l 选项来指定静态库,使用 -L 参数来指定库文件的搜索路径。比如上述例子应指定 -lmylib,所有库文件名都以 lib 开头,开头的 lib 在指定参数时应省略。-l 和 -L 之后都直接带参数而不跟空格。
在使用 gcc 时,要注意其参数的顺序。-l 是链接器选项,一定要放在被编译的文件名称之后;若放在文件名称之前则会连接失败,并会出现莫名其妙的错误。下面来实战一下。
生成实例
通用步骤:
通过代码生成 *.o 编译后文件
gcc -c -fPIC (-Iinclude) *c -I 为包含头文件路径,include 为 *.c 源码包含的头文件路径
使用 ar 将 .o 打包成 .a 静态库
ar rcs lib*.a *.o 可以使用 nm -a *.a 查看静态库内容
将 *.a 打包到应用程序中直接使用
gcc main.c (-Iinclude) lib/lib*.a -o app -I 包含头文件路径,lib*.a 为静态库名,前面的 lib 为静态库的路径
代码及路径
库头文件
路径: /include/math.h
#include <stdio.h>
int add(int i,int j);
int sub(int i, int j);
库实现文件
路径: /src/math.c
#include "../include/math.h"
int add(int i,int j)
{
return i+j;
}
int sub(int i,int j)
{
return i*j;
}
调用文件
路径: ./main.c
#include <stdio.h>
#include "include/math.h"
int main(void)
{
int a = add(5,4);
printf("%d\n",a);
int s = sub(5,4);
printf("%d\n",s);
return 0;
}
编译流程
将 .c 生成 .o 中间文件
gcc -c -fPIC -Iinclude ./srcmath.c 当前路径位于main.c, 生成 math.o
根据 .o 生成对应的 .a 静态库
ar rcs libmath.a math.o 文件下生成 libmath.a 静态库文件 查看静态内容 nm -a libmath.a
将 *.a 打包进入可执行文件中
gcc main.c (-Iinclude) lib/libmath.a -o app 步骤2将生成静态库放到了主目录下lib下
执行查看结果
执行结果输出 Allies:soku rememberme$ ./app 9 20
动态库
共享库,是在执行程序启动时加载到执行程序中,可以被多个执行程序共享使用。建议库开发人员创建共享库,比较明显的优势在于库是独立的,便于维护和更新;而静态库的更新比较麻烦,一般不做推荐。然而,它们又各有优点,后面会讲到。
Linux下的库均采用的是动态库形式,便于更新以及各个程序都可无缝调用,而且还节省空间,优点出众,下面我们来看如何生成动态库并实施运行。
共享库的创建比较简单,基本有两步。首先使用 -fPIC 或 -fpic 创建目标文件,PIC 或 pic 表示位置无关代码,然后就可以使用以下格式创建共享库了:
gcc -share -Wl,-soname,lib*.so.1 -o lib*.so.1.01 *.o
注意:
- -Wl,-soname,lib*.so.1之间没有空格
- *.o 后面可以跟 .o 多个文件
对于 so 库的默认名字的解释
real name: lib*.so.1.01 真实的so库名 so name: lib*.so.1 只含有大版本的so库名 link name: lib*.so 编译链接阶段使用,方便Makefile管理 link name 使用命令 ln -s lib*.so.1.01 lib.*.so 来创建
生成实例
通用步骤:
源库文件 .c 生成 .o 中间文件
gcc -c -fPIC (-Include) *.c
将 .o 生成动态库 .so
gcc -share -Wl,-soname,lib*.so.1 -o lib*.so.1.01 *.o
将动态库链接到可执行主文件中
gcc main.c lib/lib*.so -o app 已建立动态库 real name 的 link name 库
设置共享库配置文件
1. 打开指定配置文件 sudo vim /etc/ld.conf 2. 添加动态库所在路径 3. 更新动态库配置文件 sudo ldconfig -v
编译流程
源库文件 .c 生成 .o 中间文件
gcc -c -fPIC -Include ./src/math.c
将 .o 生成动态库 .so
gcc -shared -Wl,-soname,libmath.so.1 -o libmath.so.1.01 math.o linux端测试成功,mac端失败,使用如下命令(不含版本号信息) gcc -shared -Wl -o libmath.so math.o
将动态库链接到可执行主文件中
gcc main.c lib/lib*.so -o app1 已建立动态库 real name 的 link name 库 ldd app1 测试程序可否找到共享库
设置共享库配置文件(Linux配置,Mac测试无需配置)
1. 打开指定配置文件 sudo vim /etc/ld.conf 2. 添加动态库所在的绝对路径 /home/allies/linux/shared/lib 3. 更新动态库配置文件 sudo ldconfig -v
运行查看结果
Allies:soku rememberme$ ./app1 9 20
Linux下共享库的加载
在所有基于 GNUglibc 的系统中,在启动一个 ELF 二进制执行程序时, 一个特殊的程序”程序装载器”会被自动装载并运行。在 linux 中,这个程序装载器就是/lib/ld-linux.so.X(X 是版本号)。它会查找并装载应用程序所依赖的所有共享库。被搜索的目录保存在/etc/ld.so.conf 文件中。当然,如果程序的每次启动,都要去搜索一番,势必效率不堪忍受。Linux 系统已经考虑这一点,对共享库采用了缓存管理。ldconfig 就是实现这一功能的工具,其缺省读取/etc/ld.so.conf 文件,对所有共享库按照一定规范建立符号连接,然后将信息写入 /etc/ld.so.cache。/etc/ld.so.cache 的存在大大加快了程序的启动速度。
总结
共享库下静态库和动态库都已实现,总结一下两者的优缺点:
共享库 | 优点 | 缺点 |
---|---|---|
静态库 | 库文件直接打入代码中,linux下无需修改动态库配置文件直接使用 | 代码体积比较大,升级迭代麻烦 |
动态库 | 代码中使用相对地址寻址,值友符号记录表和库版本号,升级迭代容易,代码体积小 | 主机必须含有共享库,且版本一致,linux下需要修改动态库搜寻配置文件 |
邢文鹏Linux教学资料