1. 程式人生 > >gcc 動態連結與靜態連結

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.alibfoo.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的錯誤,程式無法正常執行。