一、gcc/g++命令中關於庫的引數:
-shared: 該選項指定生成動態連線庫;
-wl,-rpath: 記錄以來so檔案的路徑資訊。ld_library_path:這個環境變數指示動態聯結器可以裝載動態庫的路徑。
當然如果有root許可權的話,可以修改/etc/ld.so.conf檔案,然後呼叫 /sbin/ldconfig來達到同樣的目的,不過如果沒有root許可權,那麼只能採用修改ld_library_path環境變數的方法了。
呼叫動態庫的時候,有幾個問題會經常碰到:明明已經將庫的標頭檔案所在目錄 通過 「-i」 include進來了,庫所在檔案通過 「-l」引數引導,並指定了「-l」的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈結的so檔案,這時你要作的就是通過修改 ld_library_path或者/etc/ld.so.conf檔案來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈結的問題了。
1. ld會去找gcc/g++命令中的引數-l;2. 再找gcc的環境變數library_path,它指定程式靜態鏈結庫檔案搜尋路徑;
export library_path=$library_path:data/home/billchen/lib
3. 再找預設庫目錄 /lib /usr/lib /usr/local/lib,這是當初compile gcc時寫在程式內的。
三、動態鏈結時、執行時搜尋路徑順序:1. 編譯目標**時指定的動態庫搜尋路徑;
2. 環境變數ld_library_path指定動態庫搜尋路徑,它指定程式動態鏈結庫檔案搜尋路徑;
export ld_library_path=$ld_library_path:data/home/billchen/lib3. 配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑;
4. 預設的動態庫搜尋路徑/lib;
5. 預設的動態庫搜尋路徑/usr/lib。
當乙個庫同時存在靜態庫和動態庫時,比如libmysqlclient.a和libmysqlclient.so同時存在時:
在linux下,動態庫和靜態庫同事存在時,gcc/g++的鏈結程式,預設鏈結的動態庫。
可以使用下面的方法,給聯結器ld傳遞引數,看是否鏈結動態庫還是靜態庫。-wl,-bstatic -llibname //指定讓gcc/g++鏈結靜態庫
使用:gcc/g++ test.c -o test -wl,-bstatic -llibname -wl,-bdynamic -lm -lc
-wl,-bdynamic -llibname //指定讓gcc/g++鏈結動態庫
使用:gcc/g++ test.c -o test -wl,-bdynamic -llibname
如果要完全靜態加在,使用-static引數,即將所有的庫以靜態的方式鏈入可執行程式,這樣生成的可執行程式,不再依賴任何庫,同事出現的問題是,這樣編譯出來的程式非常大,占用空間。
如果不使用-wl,-bdynamic -lm -c會有如下錯誤:
[chenbaihu@build17 lib]$ lslibtest.a libtest.so t t.cc test.cc test.h test.o
[chenbaihu@build17 lib]$ g++ -wall -g t.cc -o t -l./ -wl,-bstatic -ltest -wl,-bdynamic -lm -lc
[chenbaihu@build17 lib]$ g++ -wall -g t.cc -o t -l./ -wl,-bstatic -ltest
/usr/bin/ld: cannot find -lm
collect2: ld 返回 1
五、有關環境變數:
六、庫的依賴問題:
比如我們有乙個基礎庫libbase.a,還有乙個依賴libbase.a編譯的庫,叫做libchild.a;在我們編譯程式時,一定要先-lchild再-lbase。 如果使用 -lbase -lchild,在編譯時將出現一些函式undefined,而這些函式實際上已經在base中已經定義;
為什麼會有庫的依賴問題?一、靜態庫解析符號引用:
鏈結器ld是如何使用靜態庫來解析引用的。在符號解析階段,鏈結器從左至右,依次掃瞄可重定位目標檔案(*.o)和靜態庫(*.a)。
集合e:可重定位目標檔案(*.o檔案)的集合。
集合u:未解析(未定義)的符號集,即符號表中undef的符號。
集合d: 已定義的符號集。
初始情況下,e、u、d均為空。
1、對於每個輸入檔案f,如果是目標檔案(.o),則將f加入e,並用f中的符號表修改u、d(在檔案f中定義實現的符號是d,在f中引用的符號是u),然後繼續下個檔案。
2、如果f是乙個靜態庫(.a),那麼鏈結器將嘗試匹配u中未解析符號與靜態庫成員(靜態庫的成員就是.o檔案)定義的符號。如果靜態庫中某個成員m(某個.o檔案)定義了乙個符號來解析u中引用,那麼將m加入e中,
3、當所有輸入檔案完成後,如果u非空,鏈結器則會報錯,否則合併和重定位e中目標檔案,構建出可執行檔案。
到這裡,為什麼會有庫的依賴問題已經得到解答:
因為libchild.a依賴於libbase.a,但是libbase.a在libchild.a的左邊,導致libbase.a中的目標檔案(*.o)根本就沒有被載入到e中,所以解決方法就是交換兩者的順序。當然也可以使用-lbase -lchild -lbase的方法。
參考文章:
七、動態庫公升級問題:
在動態鏈結庫公升級時,解決方法:解決的辦法是採用「rm+cp」 或「mv+cp」 來替代直接「cp」 的操作方法。不能使用cp newlib.so oldlib.so,這樣有可能會使程式core掉;
而應該使用:
rm oldlib.so 然後 cp newlib.so oldlib.so
或者mv oldlib.so oldlib.so_bak 然後 cp newlib.so oldlib.so
如果直接用cp newlib.so oldlib.so時,在替換so檔案時,如果在不停程式的情況下,直接用 cp new.so old.so 的方式替換程式使用的動態庫檔案會導致正在執行中的程式崩潰。
八、程式不停止,更新動態庫檔案
1、為什麼在不停程式的情況下,直接用 cp 命令替換程式使用的 so 檔案,會使程式崩潰? 很多同學在工作中遇到過這樣乙個問題,在替換 so 檔案時,如果在不停程式的情況下,直接用cp new.so old.so的方式替換程式使用的動態庫檔案會導致正在執行中的程式崩潰,退出。這與 cp 命令的實現有關,cp 並不改變目標檔案的 inode,cp 的目標檔案會繼承被覆蓋檔案的屬性而非原始檔。實際上它是這樣實現的: strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so open("libnew.so", o_rdonly|o_largefile) = 3 open("libold.so", o_wronly|o_trunc|o_largefile) = 4 在 cp 使用「o_wronly|o_trunc」 開啟目標檔案時,原 so 檔案的映象被意外的破壞了。這樣動態鏈結器 ld.so 不能訪問到 so 檔案中的函式入口。從而導致 segmentation fault,程式崩潰。ld.so 載入 so 檔案及「再定位」的機制比較複雜。
2、怎樣在不停止程式的情況下替換so檔案,並且保證程式不會崩潰? 答案是採用「rm+cp」 或「mv+cp」 來替代直接「cp」 的操作方法。在用新的so檔案 libnew.so 替換舊的so檔案 libold.so 時,如果採用如下方法: rm libold.so //如果核心正在使用libold.so,那麼inode節點不會立刻別刪除掉。 cp libnew.so libold.so 採用這種方法,目標檔案 libold.so 的 inode 其實已經改變了,原來的 libold.so 檔案雖然不能用"ls"檢視到,但其inode並沒有被真正刪除,直到核心釋放對它的引用。(即: rm libold.so,此時,如果ld.so正在加在libold.so,核心就在引用libold.so的inode節點,rm libold.so的inode並沒有被真正刪除,當ld.so對libold.so的引用結束,inode才會真正刪除。這樣程式就不會崩潰,因為它還在使用舊的libold.so,當下次再使用libold.so時,已經被替換,就會使用新的libold.so)同理,mv只是改變了檔名,其 inode 不變,新檔案使用了新的 inode。這樣動態鏈結器 ld.so 仍然使用原來檔案的 inode 訪問舊的 so 檔案。因而程式依然能正常執行。(即: mv libold.so ***後,如果程式使用動態庫,還是使用舊的inode節點,當下次再使用libold.so時,就會使用新的libold.so)到這裡,為什麼直接使用「cp new_exec_file old_exec_file」這樣的命令時,系統會禁止這樣的操作,並且給出這樣的提示「cp: cannot create regular file `old': text file busy」。這時,我們採用的辦法仍然是用「rm+cp」或者「mv+cp」來替代直接「cp」,這跟以上提到的so檔案的替換有同樣的道理。但是,為什麼系統會阻止cp覆蓋可執行程式,而不阻止覆蓋so檔案呢?這是因為 linux 有個 demand paging 機制,所謂「demand paging」,簡單的說,就是系統為了節約物理記憶體開銷,並不會程式執行時就將所有頁(page)都載入到記憶體中,而只有在系統有訪問需求時才將其載入。「demand paging」要求正在執行中的程式映象(注意,並非檔案本身)不被意外修改,因此核心在啟動程式後會鎖定這個程式映象的 inode。對於 so 檔案,它是靠 ld.so 載入的,而ld.so畢竟也是使用者態程式,沒有權利去鎖定inode,也不應與核心的檔案系統底層實現耦合。
Linux C程式設計的庫使用
從邏輯功能上看,程式的主體是由一系列函式組成的,所以編寫程式的主要工作之一是實現函式。為了有效降低程式設計的工作量,程式設計系統會把一些非常基本 常用的函式集中到函式庫中實現,如資訊的列印函式 檔案的開啟或者關閉函式 記憶體空間的申請與釋放函式 數學計算函式等。當程式需要使用函式庫中某個函式時,就可...
linux下使用名mysql資料庫程式設計
1介面 import mysqldb 2.鏈結資料庫 conn mysqldb.connect 3 獲取資料庫指標 因該模組底層其實是呼叫c api的,所以,需要先得到當前指向資料庫的指標 cur conn.cursor 4 獲取資料庫資訊 select 先使用指標物件執行sql語句 cur.exe...
Linux程式設計 Makefile 使用
在先前的文章中,我們已經學習了 gcc 和 gdb 的使用。本節,我們將介紹 makefile 的使用。makefile帶來的好處就是 自動化編譯 一但寫好,只需要乙個 make 命令,整個工程便可以完全編譯,極大的提高了軟體的開發效率 特別是對於那些專案較大 檔案較多的工程 make是乙個命令工具...