gcc 動態連結與靜態連結
動態連結和靜態連結
在編譯Linux程式時,我們經常會看到動態連結和靜態連結這兩個術語。這兩個術語中是我Linux的共享函式庫(shared libraries)相關的。共享函式庫就象Windows系統裡的.dll檔案,它裡面包含有很多程式常用的函式。為了方便程式開發和減少程式的冗餘, 程式當中就不用包含每個常用函式的拷貝,只是在需要時呼叫系統中共享函式庫中常函式功能即可。這種方式我們稱之為動態連結(Dynamically Linked)。但有時為了程式除錯方便或其它原因,我們不希望叫程式去呼叫共享函式庫的函式,而是在函式程式碼直接連結入程式程式碼中,也就是說,在程式本 身擁有一份共享函式庫中函式的副本。這種方式我們稱之為靜態連結(Statically Linked)。
在Linux上,靜態程式庫會有類似libname.a
這樣的名稱;而共享程式庫則稱為libname.so
.x.y.z
,
此處的x.y.z
是指版本序號的樣式。
共享程式庫通常都會有連結符號指向靜態程式庫(很重要的)與相關聯的.sa
文
件。標準的程式庫會包含共享與靜態程式庫兩種格式。
你可以用ldd
(List Dynamic Dependencies)來查出某支程式需要哪些共享程式庫。
$ ldd /usr/bin/lynx
libncurses.so.1 => /usr/lib/libncurses.so.1.9.6
libc.so.5 => /lib/libc.so.5.2.18
編譯時預設搜尋庫檔案的路徑是/usr/lib,如果需要連結其它路徑下的靜態庫或共享庫檔案,需要使用-L引數加進來。
就a.out而言,以-lfoo
引數來連結,會驅使ld去尋找libfoo.sa
(shared
stubs);如果沒有成功,就會換成尋找libfoo.a
(static)。
就ELF而言, ld會先找libfoo.so
,
然後是libfoo.a
。libfoo.so
通常是一個連結符號,連結至libfoo.so.x
。
ld可能不會自動載入
libfoo.so.x,需要使用
libfoo.so的連結來指定。
編譯時相關的gcc引數
-Idir
新增標頭檔案的查詢路徑
-llibrary
指定編譯的時候使用的庫
例子用法
gcc -lcurses hello.c
#使用ncurses庫編譯程式
-Ldir
指定編譯的時候,搜尋庫的路徑。比如你自己的庫,可以用它指定目錄,不然編譯器將只在標準庫的目錄找。這個dir就是目錄的名稱。
-static
此選項將禁止使用動態庫,所以,編譯出來的東西,一般都很大,也不需要什麼
動態連線庫,就可以執行.
-share
此選項將盡量使用動態庫,所以生成檔案比較小,但是需要系統由動態庫.
庫操作命令ar和nm
ar命令
可以用來建立、修改庫,也可以從庫中提出單個模組。
庫是一單獨的檔案,裡面包含了按照特定的結構組織起來的其它的一些檔案(稱做此庫檔案的member)。原始檔案的內容、模式、時間戳、屬主、組等屬性都保留在庫檔案中。
下面是ar命令的格式:
ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files...
例如我們可以用ar rv libtest.a hello.o hello1.o來生成一個庫,庫名字是test,連結時可以用-ltest連結。該庫中存放了兩個模組hello.o和hello1.o。選項前可以有‘-'字元,也可以沒有。下面我們來看看命令的操作選項和任選項。現在我們把{dmpqrtx}部分稱為操作選項,而[abcfilNoPsSuvV]部分稱為任選項。
{dmpqrtx}中的操作選項在命令中只能並且必須使用其中一個,它們的含義如下:
* d:從庫中刪除模組。按模組原來的檔名指定要刪除的模組。如果使用了任選項v則列出被刪除的每個模組。
* m:該操作是在一個庫中移動成員。當庫中如果有若干模組有相同的符號定義(如函式定義),則成員的位置順序很重要。如果沒有指定任選項,任何指定的成員將移到庫的最後。也可以使用'a','b',或'I'任選項移動到指定的位置。
* p:顯示庫中指定的成員到標準輸出。如果指定任選項v,則在輸出成員的內容前,將顯示成員的名字。如果沒有指定成員的名字,所有庫中的檔案將顯示出來。
* q:快速追加。增加新模組到庫的結尾處。並不檢查是否需要替換。'a','b',或'I'任選項對此操作沒有影響,模組總是追加的庫的結尾處。如果使用了任選項v則列出每個模組。 這時,庫的符號表沒有更新,可以用'ar s'或ranlib來更新庫的符號表索引。
* r:在庫中插入模組(替換)。當插入的模組名已經在庫中存在,則替換同名的模組。如果若干模組中有一個模組在庫中不存在,ar顯示一個錯誤訊息,並不替換其他同名模組。預設的情況下,新的成員增加在庫的結尾處,可以使用其他任選項來改變增加的位置。
* t:顯示庫的模組表清單。一般只顯示模組名。
* x:從庫中提取一個成員。如果不指定要提取的模組,則提取庫中所有的模組。
下面在看看可與操作選項結合使用的任選項:
* a:在庫的一個已經存在的成員後面增加一個新的檔案。如果使用任選項a,則應該為命令列中membername引數指定一個已經存在的成員名。
* b:在庫的一個已經存在的成員前面增加一個新的檔案。如果使用任選項b,則應該為命令列中membername引數指定一個已經存在的成員名。
* c:建立一個庫。不管庫是否存在,都將建立。
* f:在庫中截短指定的名字。預設情況下,檔名的長度是不受限制的,可以使用此引數將檔名截短,以保證與其它系統的相容。
* i:在庫的一個已經存在的成員前面增加一個新的檔案。如果使用任選項i,則應該為命令列中 membername引數指定一個已經存在的成員名(類似任選項b)。
* l:暫未使用
* N:與count引數一起使用,在庫中有多個相同的檔名時指定提取或輸出的個數。
* o:當提取成員時,保留成員的原始資料。如果不指定該任選項,則提取出的模組的時間將標為提取出的時間。
* P:進行檔名匹配時使用全路徑名。ar在建立庫時不能使用全路徑名(這樣的庫檔案不符合POSIX標準),但是有些工具可以。
* s:寫入一個目標檔案索引到庫中,或者更新一個存在的目標檔案索引。甚至對於沒有任何變化的庫也作該動作。對一個庫做ar s等同於對該庫做ranlib。
* S:不建立目標檔案索引,這在建立較大的庫時能加快時間。
* u:一般說來,命令ar r...插入所有列出的檔案到庫中,如果你只想插入列出檔案中那些比庫中同名檔案新的檔案,就可以使用該任選項。該任選項只用於r操作選項。
* v:該選項用來顯示執行操作選項的附加資訊。
* V:顯示ar的版本。
nm 用來列出目標檔案的符號清單。
看程式碼:
1:建靜態庫
/* hellos.h */
#ifndef _HELLO_S_H
#define _HELLO_S_H
void printS(char* str);
#endif
/* hellos.c */
#include "hellos.h"
void printS(char* str) {
printf("print in static way: %s", str);
}
輸入命令:
gcc -c -o hellos.o hellos.c
ar cqs libhellos.a hellos.o
於是得到了libhellos.a這麼一個靜態連結庫
2:主程式
/* main.c */
#include <stdio.h>
#include "hellos.h"
main() {
char* text = "Hello World!/n";
printS(text);
}
編譯連結:
gcc -o hello main.c -static -L. -lhellos
然後執行hello可以看到輸出
print in static way: Hello World!
刪除libhellos.a和hellos.*後, 程式仍然正常執行。
下面再來看動態連結
3:建動態庫
/* hellod.h */
#ifndef _HELLO_D_H
#define _HELLO_D_H
void printD(char* str);
#endif
/* hellod.c */
#include "hellod.h"
void printD(char* str) {
printf("print in dynamic way: %s", str);
}
輸入命令:
gcc -shared -o libhellod.so hellod.c
於是得到了libhellod.so這麼一個動態連結庫
4:主程式
/* main.c */
#include <stdio.h>
#include "hellod.h"
main() {
char* text = "Hello World!/n";
printD(text);
}
編譯連結:
gcc -o hello main.c -L. -lhellod
然後執行hello可以看到輸出
print in dynamic way: Hello World!
如果這時候刪除剛剛生成的libhellod.so,再執行則會報告一個找不到libhellod.so的錯誤,程式無法正常執行。