linux下 GCC編譯鏈接靜態庫&動態庫


靜態庫

有時候需要把一組代碼編譯成一個庫,這個庫在很多項目中都要用到,例如libc就是這樣一個庫, 我們在不同的程序中都會用到libc中的庫函數(例如printf),也會用到libc中的變量(例如以后 要講到的environ變量)。本文將介紹怎么創建這樣一個庫。
這些文件的目錄結構是:

$ 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:

$ 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命令也可以為靜態庫創建索引,以上命令等價於:

$ 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選項查看。編譯器會在這些搜索路徑以及-L選項指定的路徑中查找用-l選項指定的庫,比如-lstack,編譯器會首先找有沒有共享庫libstack.so,如果有就鏈接它,如果沒有就找有沒有靜態庫libstack.a,如果有就鏈接它。所以編譯器是優先考慮共享庫的,如果希望編譯器只鏈接靜態庫,可以指定-static選項。

動態庫(共享庫)

組成共享庫的目標文件和一般的目標文件有所不同,在編譯時要加-fPIC選項,例如:

$ gcc -c -fPIC stack/stack.c stack/push.c stack/pop.c stack/is_empty.c 

-f后面跟一些編譯選項,PIC是其中一種,表示生成位置無關代碼(Position Independent Code)。
現在把main.c和共享庫編譯鏈接在一起,然后運行:

$ gcc main.c -g -L. -lstack -Istack -o main 
$ ./main 
./main: error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory

結果出乎意料,編譯的時候沒問題,由於指定了-L.選項,編譯器可以在當前目錄下找到libstack.so,而運行時卻說找不到libstack.so。那么運行時在哪些路徑下找共享庫呢?我們先用ldd命令查看可執行文件依賴於哪些共享庫:

$ ldd main        
	linux-gate.so.1 =>  (0xb7f5c000)        
	libstack.so => not found        
	libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7dcf000)        
	/lib/ld-linux.so.2 (0xb7f42000)

ldd模擬運行一遍main,在運行過程中做動態鏈接,從而得知這個可執行文件依賴於哪些共享庫,每個共享庫都在什么路徑下,加載到進程地址空間的什么地址。/lib/ld-linux.so.2是動態鏈接器,它的路徑是在編譯鏈接時指定的,gcc在做鏈接時用dynamic-linker指定動態鏈接器的路徑,它也像其它共享庫一樣加載到進程的地址空間中。libc.so.6的路徑/lib/tls/i686/cmov/libc.so.6是由動態鏈接器ld-linux.so.2在做動態鏈接時搜索到的,而libstack.so的路徑沒有找到。linux-gate.so.1這個共享庫其實並不存在於文件系統中,它是由內核虛擬出來的共享庫,所以它沒有對應的路徑,它負責處理系統調用。總之,共享庫的搜索路徑由動態鏈接器決定,從ld.so(8)的Man Page可以查到共享庫路徑的搜索順序:

  1. 首先在環境變量LD_LIBRARY_PATH所記錄的路徑中查找。
  2. 然后從緩存文件/etc/ld.so.cache中查找。這個緩存文件由ldconfig命令讀取配置文 件/etc/ld.so.conf之后生成,稍后詳細解釋。
  3. 如果上述步驟都找不到,則到默認的系統路徑中查找,先是/usr/lib然后是/lib。

先試試第一種方法,在運行main時通過環境變量LD_LIBRARY_PATH把當前目錄添加到共享庫的搜索路徑:

$ LD_LIBRARY_PATH=. ./main

這種方法只適合在開發中臨時用一下,通常LD_LIBRARY_PATH是不推薦使用的,盡量不要設置這個環境變量,理由可以參考Why LD_LIBRARY_PATH is bad

再試試第二種方法,這是最常用的方法。把libstack.so所在目錄的絕對路徑(比如/home/akaedu/somedir)添加到/etc/ld.so.conf中(該文件中每個路徑占一行),然后運行ldconfig

$ sudo ldconfig -v 

ldconfig命令除了處理/etc/ld.so.conf中配置的目錄之外,還處理一些默認目錄,如/lib、/usr/lib等,處理之后生成/etc/ld.so.cache緩存文件,動態鏈接器就從這個緩存中搜索共享庫。現在再用ldd命令查看,libstack.so就能找到了:

$ ldd main        
	linux-gate.so.1 =>  (0xb809c000)
    libstack.so => /home/akaedu/somedir/libstack.so (0xb806a000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7f0c000)
    /lib/ld-linux.so.2 (0xb8082000)

第三種方法就是把libstack.so拷到/usr/lib或/lib目錄,這樣可以確保動態鏈接器能找到這個共享庫。
其實還有第四種方法,在編譯可執行文件main的時候就把libstack.so的路徑寫死在可執行文件中:

$ gcc main.c -g -L. -lstack -Istack -o main -Wl,rpath,/home/akaedu/somedir

-Wl,-rpath,/home/akaedu/somedir表示-rpath /home/akaedu/somedir是由gcc傳遞給鏈接器的選項。可以看到readelf的結果多了一條rpath記錄:

$ readelf -a main 
... 
Dynamic section at offset 0xf10 contains 23 entries:
  Tag        Type                         Name/Value 
 0x00000001 (NEEDED)                     Shared library: 
[libstack.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000f (RPATH)                      Library rpath: [/home/akaedu/somedir]
 ...

還可以看出,可執行文件運行時需要哪些共享庫也都記錄在.dynamic段中。當然rpath這種辦法也是不推薦的,把共享庫的路徑定死了,失去了靈活性。
甚至還可以這樣寫:

$ gcc -o main main.c -g -L. -lstack -Istack  ./stack/libstack.so

參考資料:[linux_C編程一站式學習](https://book.douban.com/subject/4141733/)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM