有時候需要把一組代碼編譯成一個庫,這個庫在很多項目中都要用到,例如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.c、push.c、pop.c、is_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.a和main.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而沒有pop和is_empty。這是使用靜態庫的一個好處,鏈接器可以從靜態庫中只取出需要的部分來做鏈接。如果是直接把那些目標文件和main.c編譯鏈接在一起:
$ gcc main.c stack.o push.o pop.o is_empty.o -Istack -o main則沒有用到的函數也會鏈接進來。當然另一個好處就是使用靜態庫只需寫一個庫文件名,而不需要寫一長串目標文件名。