1. 程式人生 > >GCC與靜態庫、共享庫以及動態載入庫

GCC與靜態庫、共享庫以及動態載入庫

1.      GCC中不同型別的檔案:

字尾

內容

.a

 靜態物件庫檔案

.i

已經進行預處理的C原始檔

.o

物件檔案(-c)

.s

組合語言程式碼(-S)

.so

共享物件庫檔案

2.      常見命令列選項與結果:

命令列

結果

gcc helloworld.c

預設的gcc行為是將原始檔編譯生成物件檔案,並連線併產生一個可執行檔案,最後刪除中間產生的物件檔案。產生可執行檔案,不過名稱為預設的a.out.

gcc helloworld.c –o helloworld

-o選項用來指定最後產生的可執行檔名

gcc -c helloworld.c

預設產生檔名為helloworld.o的物件檔案,同樣可以用-o選項指定物件檔名;值得注意的是,在一條命令中可以同時產生多個物件。

gcc hellomain.c sayhello.c -o hello

將相互存在依賴關係的檔案通過編譯連結最後生成可執行檔案,以本例來講,前者依賴後者。

gcc -E helloworld.c

產生預處理程式碼,預設情況下輸出到標準輸出,同樣可以通過-o選項指定輸出到哪個檔案中,如helloworld.i

gcc -S helloworld.c

產生組合語言程式碼

3.      靜態庫、共享庫以及動態載入庫

(1) 靜態庫

靜態庫是一些目標檔案的集合,庫檔案以”.a”結尾,聯結器會將應用程式所需要用到的程式碼拷貝到應用程式中.

【如何建立與使用靜態庫】

a>    首先生成目標檔案: gcc -Wall -cmax.c

b>    建立靜態庫:ar rcs libmax.a max.o (可以根據ar –tlibmax.a檢視靜態庫中所包含的目標檔案)

c>    使用靜態庫:gcc hello_world.c libmax.a-o hello_world 或

gcc hello_world.c –L. –lmax -o hello_world

在使用靜態庫的過程中,可以指定靜態庫的全名,或者呼叫-l選項指定靜態庫的縮寫名(靜態庫全名一般是以lib開頭,後面跟靜態庫的縮寫名),但是要注意的是,在這種情況下,最好是自己通過-L選項增加庫搜尋路徑,否則編譯的時候可能會出現庫搜尋不到的錯誤。另外一點,-l

選項必須放在需要編譯的檔案之後,否則也會出現編譯錯誤!

d>    靜態庫的優缺點:

靜態庫增加了應用程式的大小,另外在處理靜態庫更新問題上需要花費更多的重編譯代價(recompile),但是理論上,靜態庫應該比共享庫或者動態載入庫執行更快(1-5%),因為它減少了在程式執行才去載入庫的開銷

(2)  共享庫:

       與靜態庫不同的是,共享庫在連結階段並不需要拷貝所需使用的程式碼,而只是做些參考標記,然後在程式啟動時載入所需要的庫檔案,因此,對比靜態庫,連結共享庫的應用程式小得多。

【共享庫的名字】

       跟共享庫相關的名字有三個,分別是soname, real name和linker name,其中soname一般用來提供版本的相容相關資訊,real name則是包含實際共享庫程式碼的檔名,linker name則是用於應用程式連結時候的一個搜尋名。一般來講,soname的命名以lib作為字首,後者則是庫名,接著是”.so”,最後則包含著週期數以及遞增的版本資訊它一般作為real name的符號連結;real name則是在soname的基礎上加上一個週期數以及最小版本資訊,和另一個週期數以及發行版本號(releasenumber).linker name則是在soname的基礎上去掉了版本號,linker namesoname的符號連結

 【如何建立與使用共享庫】

a>    首先建立物件檔案:gcc -Wall -fpic-c max.c -fpic選項是一種編譯器標誌,表示產生與記憶體位置無關的程式碼,即在產生程式碼中的過程中全部使用相對地址,而不要使用絕對         地址,其中pic代表的是position independent code.

b>    建立共享庫:

gcc -shared -Wl,-soname,libmax.so.1-olibmax.so.1.0 max.o

mv libmax.so.1.0 /opt/lib

ln -sf /opt/lib/libmax.so.1.0/opt/lib/libmax.so.1 (這條語句可以有ldonfig–n dir命令來完成)

ln -sf /opt/lib/libmax.so.1 /opt/lib/libmax.so

-shared選項是一種編譯器標誌,用來建立共享庫,soname選項則是連結器標誌,用來生成soname,在本例中,libmax.so.1是soname, libmax.so.1.0則是real name, libmax.so則是linker name.

【引申】共享庫命名與版本相容問題:

       以上面命名為例,若某個API發生改變,則所建立共享庫的realname的主版本號應該發生改變,如此時,變成libmax.so.2.0, 若只是對函式升級了函式庫而功能沒有發生改變,則只需改變次版本號,如此時變成libmax.so.1.1, 這樣可以做到版本相容.

 c> 使用共享庫:

       gcc -Wall -L/opt/lib hello_word.c -lmax-ohello_world

       使用方法類似於靜態庫,但是在應用程式執行的時候,卻有著本質的區別,例如如果我們現在執行此時生成的應用程式hello_world,則會出現下圖的錯誤資訊,那是什麼原因呢?正如前面所講,程式在執行時還得需要載入所需要的庫檔案,此時不能找到庫檔案路徑,處理這種錯誤可以採取以下兩個方法:

設定LD_LIBRARY_PATH環境變數,可以用來配置共享庫的搜尋路徑:

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH(將當前路徑加入搜尋目錄)

配置-rpath編譯選項用來表示增加一個目錄到執行時庫中,具體方法如下:

       gcc -shared-Wl,-soname,libmax.so.1,-rpath . -o libmax.so.1.0 max.o

d> 共享庫的優缺點:

       對比靜態庫,共享庫在連結階段只是對所需程式碼做些標識,在程式啟動時才會載入,這樣減少了目標應用程式的大小,通過soname,可以做到多版本的相容,這樣每次升級也不需要將原始碼全部重新編譯,免除了在升級過程中重編譯帶來的開銷;但是,因為需要在程式執行階段載入共享庫,這樣勢必需要付出執行期間的庫載入代價。

e>    共享庫相關的工具:

ldd name-of-executable  顯示執行檔案相依賴的共享庫

nm name-of-library 顯示庫(靜態或者動態)相關的識別符號(symbol)

(3)動態載入庫 (Dynamically loaded libraries)

       是指在程式執行過程中可以載入的函式庫,而不像共享庫是在程式啟動的時候載入。DLL對實現外掛和模組非常實用,因為它們執行程式在允許時等待外掛的載入,動態載入庫有自己的一套API介面去完成開啟、查詢符號,處理出錯、關閉載入庫等功能。

       void*dlopen (const char *libname, int flag);

       dlopen必須在dlerror, dlsym以及dlclose函式之前呼叫,表示要將共享庫載入到記憶體中。若所載入的共享庫還需要依賴其它庫,則必須首先載入依賴庫。若dlopen操作失敗,則返回NULL,若庫已經被載入過,則會返回同樣的函式控制代碼,引數libname一般指庫的全路徑,這樣直接載入庫檔案,若只是指定了庫檔名,則會按照以下順序進行庫的查詢:

a.      根據環境變數LD_LIBRARY_PATH查詢;

b.      根據/etc/ld/so.cache目錄查詢;

c.      依次在/lib和/usr/lib目錄查詢;

         flag引數表示處理未定義函式的方式,可以使用RTLD_LAZYRTLD_NOW,前者表示暫時不處理未定義的函式,先把庫載入在記憶體中,等遇到未定義的函式再說;後者表示立馬檢查是否存在未定義的函式,若存在,則以失敗告終。

       void*dlsym(void *handle, const char *symbol);

       dlsym可以獲得指定函式(根據symbol)在記憶體中的位置(指標)。若找不到函式,則返回NULL。

       Int dlclose(void *);

       dlclose可以關閉一個已經載入的庫,若存在解構函式,則解構函式會呼叫。