摘自:Linux C編程一站式學習html
透過本節能夠學會編譯靜態連接庫的shell腳本!linux
有時候須要把一組代碼編譯成一個庫,這個庫在不少項目中都要用到,例如libc
就是這樣一個庫,咱們在不一樣的程序中都會用到libc
中的庫函數(例如printf
),也會用到libc
中的變量(例如之後要講到的environ
變量)。本節介紹怎麼建立這樣一個庫。shell
咱們繼續用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
:spa
$ 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
命令也能夠爲靜態庫建立索引,以上命令等價於:code
$ ar r libstack.a stack.o push.o pop.o is_empty.o $ ranlib libstack.a
而後咱們把libstack.a
和main.c
編譯連接在一塊兒:htm
$ gcc main.c -L. -lstack -Istack -o main
-L
選項告訴編譯器去哪裏找須要的庫文件,-L.
表示在當前目錄找。-lstack
告訴編譯器要連接libstack
庫,-I
選項告訴編譯器去哪裏找頭文件。注意,即便庫文件就在當前目錄,編譯器默認也不會去找的,因此-L.
選項不能少。編譯器默認會找的目錄能夠用-print-search-dirs
選項查看:blog
$ 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
則沒有用到的函數也會連接進來。固然另外一個好處就是使用靜態庫只需寫一個庫文件名,而不須要寫一長串目標文件名。