一、库文件
预先编译好的方法的集合。在Linux下有两种库文件:静态库与共享库。
静态库是以 libxxx.a
的形式存在的库文件,在程序编译时(使用到的方法)会加载到程序中成为程序的一部分。
共享库是以 libxxx.so
的形式存在的库文件,在程序编译时不会加载的程序中,在程序运行时才进行加载。
两种库文件各有优缺点,下表简单的归纳了以下两种库文件的特点:
(在windows中的库文件也是有静态库(xx.lib)和动态库(xx.dll) )
库 | 实现机制 | 优点 | 缺点 |
---|---|---|---|
静态库libname.a | 将库文件中使用到的方法写入到可执行文件中 | 1. 程序一次编译完成,运行时无需额外加载,运行相对较快 | 1.可执行程序相对较大,占用磁盘空间较大 2.程序运行时,库文件部分会被加载到内存,增加了内存的消耗 3.程序的每次更新都需要将整个程序重新编译一遍,维护不方便 |
共享库libname.so | 将库文件中使用到的方法在可执行文件中形成一份引用,在执行是加载 | 1.可执行程序相对较小,占用磁盘空间小 2.程序运行时,用到库文件部分在内存中与其他程序共享一份“库”,节省内存消耗 3.程序更新时,只需重新编译动态库 | 1.每次运行都需要加载动态库,运行速度相对较慢 |
由于现在计算机的运行速度都很快,静态库的优点已不是那么明显了。但是一般来说针对一些小的可执行程序通常使用静态库,这样我们写好的程序提供给别人使用时就会很方便。
二、标准目录
在linux下,一般头文件放在 /usr/include/
目录下。在C程序中使用 <xxx.h>
尖括号括起来的头文件,自动在该目录下检索对应的头文件。使用 "xxx.h"
引号括起来的头文件优先在程序所在目录检索对应头文件,找不到再在/usr/include/
下查找。如果使用的头文件不再这两种目录内,在编译时使用 -I
参数指定头文件路径。
可执行程序/命令一般放在 /bin
、 /usr/bin/
目录下。同理我们在终端执行命令时优先在这些目录查找,所以我们在执行自己写的程序时通常会在程序前指定路径,如~/run
执行自己家目录下的run程序,./run
执行当前目录下的run程序。
库文件一般存放在 /lib
、/usr/lib/
目录下。如果我们使用的是标准库文件,我们在编译时无需指定库文件路径即可编译成功。如果我们使用的是自己制作的库文件可使用参数 -L
、-l
的方式指定库文件路径和库名。在使用共享库时,通常把自己的库文件放在标准库文件目录下。
创建库文件:
创建静态库文件
使用ar命令创建静态库文件 ar crv libname.a 1.o 2.o .....
c – 创建库、r – 把方法添加到库中、v – 显示信息。
其中 libname.a
,lib是库名前缀,库名是“name”,在使用是通过 “-l+库名”的方式使用。
下面是有关ar
命令的参数:
$ ar -h
用法:ar [仿真选项] [-]{dmpqrstx}[abcDfilMNoPsSTuvV] [--plugin <名称>] [成员名] [计数] 归档 文件…
ar -M [<mri-脚本]
命令:
d - 从归档文件中删除文件
m[ab] - 在归档文件中移动文件
p - 打印在归档文件中找到的文件
q[f] - 将文件快速追加到归档文件中
r[ab][f][u] - 替换归档文件中已有的文件或加入新文件
s - 作为 ranlib 工作
t - 显示归档文件的内容
x[o] - 从归档文件中分解文件
特定命令修饰符:
[a] - 将文件置于 [成员名] 之后
[b] - 将文件置于 [成员名] 之前 (于 [i] 相同)
[D] - 将 0 用于时间戳和 uid/gid(默认)
[D] - 使用实际时间戳和 uid/gid
[N] - 使用名称的实例 [数量]
[f] - 截去插入的文件名称
[P] - 在匹配时使用完整的路径名
[o] - 保留原来的日期
[u] - 只替换比当前归档内容更新的文件
通用修饰符:
[c] - 不在必须创建库的时候给出警告
[s] - 创建归档索引 (cf. ranlib)
[S] - 不要创建符号表
[T] - 产生一个简单归档
[v] - 输出较多信息
[V] - 显示版本号
创建共享库文件
使用 gcc 可以创建动态库,gcc -shared -fPIC -o libname.so 1.o 2.o ...
其中,-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。
三、库文件实例
下面通过三个实例来了解静态库与共享库。
实例一:多文件编译
在 a.c
文件中:
#include <stdio.h>
/* a.c file */
void afun()
{
printf("this is a.c file\n");
}
在 b.c
文件中:
#include <stdio.h>
/* b.c file */
void bfun()
{
printf("this is b.c file\n");
}
在 main.c
文件中:
#include <stdio.h>
/* 声明外部函数 */
void afun();
void bfun();
int main()
{
afun();
bfun();
return 0;
}
编译程序并运行:
实例二:制作静态库
使用 ar crv libfile.a a.o b.o
命令将之前的 a.o
与 b.o
文件制作成为静态库。
将 main.o
文件与 libfile.a
库文件放在同一个目录下,进行链接操作。
gcc -o run main.c -L. -lfile
参数-L
指定库文件位置,参数 -l
指定库名。
实例三:制作共享库
使用 gcc -shared -fPIC -o libfile.so a.o b.o
命令将之前的 a.o
与 b.o
文件制作成为共享库。
可以看到 libfile.so
共享库文件已经创建完成。
下面我们创建一个 test2 目录,把共享库文件和 main 主程序相关文件 拷贝到该目录下。
现在我们使用共享库对程序进行链接。同样的使用命令 gcc -o run main.c -L. -lfile
。参数-L
指定库文件位置,参数 -l
指定库名。
执行链接后的 run 可执行程序。
可以看到执行失败了,提示信息为“libfile.so: cannot open shared object file: No such file or directory”。提示我们没有找到 libfile.so 库文件。还记得上文提到的共享库的特点吗,在运行时才加载,编译时知识生成一份引用。
我们用ldd
命令查看程序使用了哪些共享库。
这里显示libfile.so
文件因为不在标准库文件目录下,所以没有找到库文件的地址。其他三个库文件所在的目录都是在环境变量中设置好的搜索路径。
我们将libfile.so
动态库添加到 /usr/lib/
标准库文件目录下。(管理员模式)
我们在管理员模式下,使用 mv libfile.so /usr/lib/
命令将 libfile.so 文件剪切到 /usr/lib/
目录下。现在使用 ldd 命令查看程序所需的共享库文件是否可以找到。并测试程序是否可以成功运行。
实例四:优先使用共享库
在链接时,如果静态库与共享库同名,优先使用共享库进行编译。
我们将两种库文件放在同一目录下,对主程序文件进行链接,查看生成的可执行程序。
如图所示,在 test3 目录下有三个文件,分别是 libfile.a
静态库文件、libfile.so
共享库文件、main.o
主程序文件。使用 gcc -o run main.o -L. -lfile
命令对程序进行链接,生成run程序。
注:ldd 命令只可以查看程序使用的共享库的情况,这里显示 libfoo.so
,证明使用了共享库文件 libfile.so
。