Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3. 静态库

有时候需要把一组代码编译成一个库,这个库在很多项目中都要用到,例如 libc 就是这样一个库,我们在不同的程序中都会用到 libc 中的库函数(例如 printf ),也会用到 libc 中的变量(例如以后要讲到的 environ 变量)。本节介绍怎么创建这样一个库。

我们继续用 stack.c 的例子。为了便于理解,我们把 stack.c 拆成四个程序文件(虽然实际上没太大必要),把 main.c 改得简单一些,头文件 stack.h 不变,本节用到的代码如下所示:

/* stack.c */
char stack[512];
int top = -1;
/* push.c */
extern char stack[512];
extern int top;

void push(char c)
{
	stack[++top] = c;
}
/* pop.c */
extern char stack[512];
extern int top;

char pop(void)
{
	return stack[top--];
}
/* is_empty.c */
extern int top;

int is_empty(void)
{
	return top == -1;
}
/* stack.h */
#ifndef STACK_H
#define STACK_H
extern void push(char);
extern char pop(void);
extern int is_empty(void);
#endif
/* main.c */
#include <stdio.h>
#include "stack.h"

int main(void)
{
	push('a');
	return 0;
}

这些文件的目录结构是:

$ tree
.
|-- main.c
`-- stack
    |-- is_empty.c
    |-- pop.c
    |-- push.c
    |-- stack.c
    `-- stack.h

1 directory, 6 files

我们把 stack.cpush.cpop.cis_empty.c 编译成目标文件:

$ gcc -c stack/stack.c stack/push.c stack/pop.c stack/is_empty.c

然后打包成一个静态库 libstack.a

$ ar rs libstack.a stack.o push.o pop.o is_empty.o
ar: creating libstack.a

库文件名都是以 lib 开头的,静态库以 .a 作为后缀,表示 Archive。 ar 命令类似于 tar 命令,起一个打包的作用,但是把目标文件打包成静态库只能用 ar 命令而不能用 tar 命令。选项 r 表示将后面的文件列表添加到文件包,如果文件包不存在就创建它,如果文件包中已有同名文件就替换成新的。 s 是专用于生成静态库的,表示为静态库创建索引,这个索引被链接器使用。 ranlib 命令也可以为静态库创建索引,以上命令等价于:

$ ar r libstack.a stack.o push.o pop.o is_empty.o
$ ranlib libstack.a

然后我们把 libstack.amain.c 编译链接在一起:

$ gcc main.c -L. -lstack -Istack -o main

-L 选项告诉编译器去哪里找需要的库文件,-L. 表示在当前目录找。-lstack 告诉编译器要链接libstack 库,-I 选项告诉编译器去哪里找头文件。注意,即使库文件就在当前目录,编译器默认也不会去找的,所以-L. 选项不能少。编译器默认会找的目录可以用-print-search-dirs 选项查看:

$ gcc -print-search-dirs
install: /usr/lib/gcc/i486-linux-gnu/4.3.2/
programs: =/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/:/usr/libexec/gcc/i486-linux-gnu/4.3.2/:/usr/libexec/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/bin/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/bin/
libraries: =/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/lib/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/lib/../lib/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/:/lib/i486-linux-gnu/4.3.2/:/lib/../lib/:/usr/lib/i486-linux-gnu/4.3.2/:/usr/lib/../lib/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/lib/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../:/lib/:/usr/lib/

其中的 libraries 就是库文件的搜索路径列表,各路径之间用 : 号隔开。编译器会在这些搜索路径以及 -L 选项指定的路径中查找用 -l 选项指定的库,比如 -lstack ,编译器会首先找有没有共享库 libstack.so ,如果有就链接它,如果没有就找有没有静态库 libstack.a ,如果有就链接它。所以编译器是优先考虑共享库的,如果希望编译器只链接静态库,可以指定 -static 选项。

那么链接共享库和链接静态库有什么区别呢?在第 2 节 “ main 函数和启动例程”讲过,在链接 libc 共享库时只是指定了动态链接器和该程序所需要的库文件,并没有真的做链接,可执行文件 main 中调用的 libc 库函数仍然是未定义符号,要在运行时做动态链接。而在链接静态库时,链接器会把静态库中的目标文件取出来和可执行文件真正链接在一起。我们通过反汇编看上一步生成的可执行文件 main

$ objdump -d main
...
08048394 <main>:
 8048394:       8d 4c 24 04             lea    0x4(%esp),%ecx
 8048398:       83 e4 f0                and    $0xfffffff0,%esp
 804839b:       ff 71 fc                pushl  -0x4(%ecx)
...
080483c0 <push>:
 80483c0:       55                      push   %ebp
 80483c1:       89 e5                   mov    %esp,%ebp
 80483c3:       83 ec 04                sub    $0x4,%esp

有意思的是, main.c 只调用了 push 这一个函数,所以链接生成的可执行文件中也只有 push 而没有 popis_empty 。这是使用静态库的一个好处,链接器可以从静态库中只取出需要的部分来做链接。如果是直接把那些目标文件和 main.c 编译链接在一起:

$ gcc main.c stack.o push.o pop.o is_empty.o -Istack -o main

则没有用到的函数也会链接进来。当然另一个好处就是使用静态库只需写一个库文件名,而不需要写一长串目标文件名。