linux下制作静态库

前言

在《一文带你了解静态库和动态库》一文中介绍了静态库的特点以及与动态库的区别。那么你有没有想过如何把自己写好的函数接口制作成静态库给别人用呢?本文教你如何制作属于自己的静态库。

编译成可重定位文件

在《一文带你了解静态库和动态库》简单介绍了可重定位文件。其中也有一位非常细心的读者发现,在ubuntu18.04的系统,使用gcc7.4编译出来的可执行文件的type是DYN,这是编译器生成了一种位置无关的可执行文件(PIE),它类似于动态库,其地址在加载时确定,从而更加安全。本文不再展开介绍。

本文实例代码test1.c代码如下:

1
2
3
4
5
6
//来源:公众号【编程珠玑】 网站:https://www.yanbinghu.com
#include"test1.h"
void test1()
{
printf("I am test1\n");
}

编译成可重定位文件,即生成.o文件:

1
2
3
4
5
6
7
8
9
10
11
12
$ gcc -c test1.c
$ readelf -h test1.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
(省略部分内容)

关于编译的几个阶段,可以参考《代码是如何变成可执行文件的》。

制作成静态库

为了制作成静态库,我们需要使用ar命令。

1
2
3
$ ar -rcs libtest1.a test1.o   #库名一般以.a为扩展名,以lib开头
$ ar -t libtest1.a #查看内容
test1.o

通常来说,静态库以.a作为后缀,且以lib开头。至此就将我们提供的test1函数做成了静态库,但是为了方便其他人使用,我们再提供一个头文件test1.h,代码如下:

1
2
#include<stdio.h>
void test1();

这个时候就可以将我们做好的静态库给其他人使用啦。

使用静态库

我们写一个main.c来调用test1():

1
2
3
4
5
6
7
//来源:公众号【编程珠玑】 网站:https://www.yanbinghu.com
#include"test1.h"
int main(void)
{
test1();
return 0;
}

编译运行:

1
2
3
$ gcc -o main main.c -L ./  -ltest1
$ ./main
I am test1

其中-L用于指定链接库的路径,由于我们要链接的库名为libtest1.a,在链接的时候,去掉开头的lib和后缀.a,前面再加l,就变成了-ltest1,其他库也是类似。例如,你如果看到程序链接使用-lm,说明它使用了名为libm.a的库。

再看静态库使用

如果这时候还有一个库libtest0.a,库中调用了test1.c的函数,而main函数调用了libtest0.a中的函数呢?即,假设有test0.c中调用test1(),且两者位于不同的库中,test0.c代码如下:

1
2
3
4
5
6
7
#include"test0.h"
void test0()
{
printf("I am test0,I will call test1\n");
test1();
printf("test0 call test1 end\n");
}

头文件test0.h:

1
2
#include"test1.h"
void test0();

还是以类似的方法制作静态库libtest0.a:

1
2
3

$ gcc -c test0.c
$ ar -rcs libtest0.a test0.o

改写main.c:

1
2
3
4
5
6
7
//来源:公众号【编程珠玑】 网站:https://www.yanbinghu.com
#include"test0.h"
int main(void)
{
test0();
return 0;
}

重新编译链接:

1
2
3
4
$ gcc -o main main.c -L ./ -ltest1 -ltest0
.//libtest0.a(test0.o): In function `test0':
test0.c:(.text+0x14): undefined reference to `test1'
collect2: error: ld returned 1 exit status

这里我们发现编译出错了,提示test1未定义,很显然是由于test0中调用了test1。至于解决办法也很简单,调整链接库的顺序即可,更加详细的原因可以参考《一个奇怪的链接问题》和《静态库和动态库的区别》。

我们调整之后再次编译链接并运行:

1
2
3
4
5
$ gcc -o main main.c -L ./ -ltest0 -ltest1
$ ./main
I am test0,I will call test1
I am test1
test0 call test1 end

可以看到,在调整两个库的顺序之后,编译链接正常,并且程序也按照我们预期的结果运行。

因此,我们在链接时,应该尽量把被需要的库放在后面

本文作者:守望
来源:https://www.yanbinghu.com

ar命令详解

从前面的内容我们可以观察到,我们是通过ar命令来制作静态库(归档文件)的,它可以将多个按照一定的规则组织在一起。我们再来了解一下ar命令,ar命令常见参数如下:

  • r 向归档文件中添加内容,如原先已存在,则替换
  • c 创建归档文件
  • s 添加索引信息
  • d 从归档文件中删除
  • t 查看归档文件的内容
  • x 解压归档文件
  • a/b 向归档文件中添加内容
  • v 显示详细信息

rcs参数我们已经在前面用到了。-a(after)或者-b(before)参数可以向归档文件中添加文件,例如:

1
$ ar -ra test0.o libtest0.a test1.o

这里表示在libtest0.a中的test0.o之后,添加test1.o。
添加后内容如下:

1
2
3
$ ar -t libtest0.a
test0.o
test1.o

当然了,归档文件是可以解开的,比如:

1
2
3
$ ar -xv libtest0.a 
x - test0.o
x - test1.o

你要删除其中的某个文件,也是没人阻止的:

1
2
$ ar -d test1.o libtest0.a 
d - test1.o

-d参数后面跟着要移除的文件。

需要特别注意的是,这里ar归档的作用并不仅仅针对可重定位目标文件,而是几乎针对任何类型的普通文件

总结

制作静态库不过是利用ar命令把一些文件可重定位文件打包在一起,其他程序在使用时需要通过链接动态将自己需要的内容“拷贝”到最终的可执行文件中。现在知道如何制作属于自己的静态库了吗?赶紧自己试试吧!

守望 wechat
关注公众号[编程珠玑]获取更多原创技术文章
出入相友,守望相助!