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_Hextern 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.h1 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.oar: 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-dirsinstall: /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/http://www.cnblogs.com/http://www.cnblogs.com/i486-linux-gnu/bin/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/http://www.cnblogs.com/http://www.cnblogs.com/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/http://www.cnblogs.com/http://www.cnblogs.com/i486-linux-gnu/lib/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/http://www.cnblogs.com/http://www.cnblogs.com/i486-linux-gnu/lib/../lib/:/usr/lib/gcc/i486-linux-gnu/4.3.2/http://www.cnblogs.com/../i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/http://www.cnblogs.com/http://www.cnblogs.com/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/http://www.cnblogs.com/http://www.cnblogs.com/i486-linux-gnu/lib/:/usr/lib/gcc/i486-linux-gnu/4.3.2/http://www.cnblogs.com/../:/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
則沒有用到的函數也會連結進來。當然另一個好處就是使用靜態庫只需寫一個庫檔案名稱,而不需要寫一長串目標檔案名。