一、討論環境
*操作系統:Redhat5/Fedora14
*編譯器:gcc 4.5.1
以下言論僅確保在以上環境中適用。別的環境,大家可以通過類比方法,得到啟示。
二、C語言頭文件的查找路徑
C語言,使用include指令,包含頭文件,但又細分兩種形式:
1、形式一:#include “file1”
gcc先在當前目錄(指包含本條#include指令的源文件所在的目錄)尋找file1,如果找不到,繼續在由-iquote選項(如果有的話)指定的目錄中尋找file1。
例如,在文件/usr/include/sys/stat.h中,包含指令#include “types.h”,那麼gcc先在/usr/include/sys目錄下尋找types.h文件。嗯,在該目錄下,確實存在一個types.h的文件。現假設我們把這個文件移動到另一個目錄:mv /usr/include/sys/types.h /bar/foo/,我們在編譯時,可以通過-iquote選項,在不改變stat.h的情況下,正常編譯(當然,通常不建議這樣做):
gcc -iquote /bar/foo -I/usr/include/sys *.o
2、形式二:#include <file2>
gcc按照以下順序查找file2:
-Idir1 -Idir2 ...
/usr/local/include
libdir/gcc/<target>/<version>/include
/usr/<target>/include
/usr/include
第一行中,-Idir1 -Idir2 ... 是用戶通過gcc的-I選項指定的目錄。值得一提的是,放在/usr/local/include/下的頭文件也會被gcc自動的檢索,這與/usr/local/lib/目錄下的庫處理方式是不一樣的(gcc的鏈接器在運行時階段不會自動查找該目錄下的庫文件,下一節會提到)。
三、C語言庫文件的查找路徑
C語言庫文件的查找路徑,又分為兩個階段:鏈接階段、運行時階段。
1、鏈接階段(link time)
此階段,需要告訴編譯器,在哪裡找到庫文件?以靜態還是動態的方式鏈接庫文件?默認情況下使用動態方式鏈接,這要求存在對應的.so動態庫文件,如果不存在,則尋找相應的.a靜態庫文件。若在編譯時向gcc傳入-static選項,則使用靜態方式鏈接,這要求所有庫文件都必須有對應的*.a靜態庫。
那麼,是否可以令某些庫使用動態鏈接,另一些庫使用靜態鏈接?不太確定,請參考ld的使用手冊,我沒有這樣用過。
切入正題,在鏈接階段,gcc編譯器如何尋找庫文件呢(linker本身並沒有默認的查找路徑,這些查找路徑是由gcc傳遞給linker的)?大家可以在編譯時,向gcc加入-v選項來觀察它向linker傳遞的庫文件查找路徑(觀察LIBRARY_PATH變量的值),通常查找路徑如下:
任何由-rpath-link或-rpath選項指定的目錄
LD_RUN_PATH(如果沒有找到-rpath或-rpath-link選項)
-Ldir1 -Ldir2 ...
/usr/lib/gcc/<target>/<version>/
/usr/lib/
第一行-rpath-link與-rpath選項的區別在於,-rpath選項指定的目錄被硬編碼到可執行文件中,-rpath-link選項指定的目錄只在鏈接階段生效。由於這兩個選項都是鏈接器ld的選項,如何從gcc中向ld傳遞這兩個選項?方法如下(更從細節參考gcc的-Wl選項):
gcc -Wl, -rpath, /usr/local/lib
這相當於向ld向傳遞了如下參數:
ld -rpath /usr/local/lib
第二行,如果沒有設置-rpath或-rpath-link選項,則查找LD_RUN_PATH環境變量指定的目錄,並把它當作-rpath選項來處理。第三行-Ldir1 -Ldir2 ...,是我們通過gcc的-L選項向其指定的庫文件查找路徑,查找順序按照我們傳遞的-L參數從左到右進行搜索;第四行屬於gcc自己的庫目錄;第五行/usr/lib/是Linux系統默認的系統庫文件的目錄。第四、第五行,都是gcc自動向linker傳遞的查找目錄。例如我現在的機器上,使用gcc -v可以看到LIBRARY_PATH變量值為:
LIBRARY_PATH=/usr/lib/gcc/i686-redhat-linux/4.5.1/:/usr/lib/
但是這並不是全部真理!根據我自己的測試,我發現gcc會把/usr/local/lib/目錄也作為鏈接階段的查找路徑,這正是問題的根源——我們在鏈接過程中,使用到了/usr/local/lib/裡面的一些庫文件,但在運行時階段,卻說找不到該庫文件。
2、運行時階段(runtime)
僅當可執行程序采用動態的方式鏈接庫文件時,才會存在運行時庫文件的查找問題。對於這種可執行程序,它本身只是記錄動態庫的名稱。所以在運行該程序時,操作系統的加載程序(ld.so)需要根據庫的名稱,在必要時加載庫文件到內存中。
在linux中,在運行時階段,動態庫(又叫共享庫)的查找路徑如下:
-rpath選項指定的目錄(已被硬編碼到可執行文件中)
LD_LIBRARY_PATH
/lib或/usr/lib
系統默認的查找路徑
我們可以通過readelf查看被硬編碼到可執行文件中的rpath:
$ readelf -d <可執行文件名> #Display the dynamic section (if present)
LD_LIBRARY_PATH則沒有這個問題,但是通常我們不建議使用這個環境變量,因為修改這個變量意味著影響所有依賴於這個環境變量的程序(如果非要使用,請把這個環境變量寫在啟動腳本中,並且讓它只影響腳本中的程序)。
那麼系統默認的查找路徑又是怎樣的?在Redhat5/Fedora14中,ld.so通過讀取/etc/ld.so.cache文件來查找庫文件的位置,如果沒有找到則繼續從/etc/ld.so.conf文件中指定的目錄查找。這個ld.so.cache文件相當於一個key-value的數據庫,key就是動態庫的名稱,value就是這些庫的存放路徑。
那麼/etc/ld.so.cache文件是怎麼生成的呢?這就要談到ldconfig這個工具程序了。ldconfig是動態鏈接庫的配置工具,使用它可以更新/etc/ld.so.cache文件,也可以查看這個文件中的key-value信息(使用ldconfig -p),ldconfig的使用細節,請參考它的使用手冊。總結一下系統默認的查找路徑:
/etc/ld.so.cache
/etc/ld.so.conf文件中指定的目錄
四、參考資料
man ld
man ldconfig
html">http://gcc.gnu.org/onlinedocs/cpp/Search-Path.html
http://www.eyrie.org/~eagle/notes/rpath.html