在前面將動態庫的知識初步學習了一下,本篇將著重講一下linux下的動態庫的編寫和使用。在gcck和 g++中,都提供了豐富的編譯選項,用來給程式設計師編譯動態加提供較多的選擇。可是,對於新手來說,如此多的編譯選項反而會讓其感覺到迷茫。所以還是那句話,先從簡單的入手,不要上去就啃大部頭,省得忙個昏天黑地反而打擊了自己的積極性。
linux下的動態庫以.so結尾,命名方式必須是以lib開頭,中間填充自己想取得動態加的名字。比如你想生成乙個叫order的排序庫,那麼最終編譯時應該是lib+order+.so(liborder.so),這個樣子。這一點不如window做的好,它可以起任何在規範下的名字,不必要如此。
另外在庫路徑下經常可以看到以後綴*.so.2之類的方式的動態庫,其實那只是一種軟鏈結,當使用ls -al命令檢視時,可以得到下面的結果:
ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.27.so
這就是說這個軟鏈結,是鏈結到後面的那個真實具體的so庫上的。通常這種用法是用來進行版本管理的,比如庫的名字為了保持一致,但為了區分不同的版本,可以軟體鏈結形成不同的符號檔案。也就是說其命名的規則可以如下:
lib + name +.so+x.y.z 後面的xyz可以用來表示主次及發行版本號等,或者自己另有約定。
另外乙個新手需要注意的是,一定要保證鏈結各方的版本一致性,這個既包括自己編寫的迭代版本,又要保證平台版本,比如,x64平台的庫和x32平台的庫就不能同時使用,否則可能編譯鏈結時就會報鏈結錯誤。
2、 -fpic編譯選項
前面已經說過,這個意思是編譯與位置無關的**。在32位上可忽略,但是在x64平台上還是要使用的。
這裡有個小細節需要注意一下,有細心的可以在一些資料中看到-fpic全小寫的這個編譯選項,這二者有什麼不同呢?相同點如前文所述都是生成與位置無關的**,重點在不同點,如果全域性偏移表(got)大小超過計算機的最大值,則-fpic不起作用,這時候兒就得用-fpic重新編譯。其中got的大小依據不同的os則有不同,比如x86上沒有限制(這意味著二者在x86是等同的),而在aarch64上為28k,其它一些具體的也有不同。換句話說,如果你在x86平台上程式設計,可以忽略這個不同。
但是為了安全起見,建議都使用-fpic來編譯自己的動態庫,防止出現跨平台使用時的一些不容易查詢的問題。
3、-w 警告資訊引數 -wl選項option傳遞給鏈結器
這個有比較多的用法,包括-wall,看名字就知道,把-w開頭的警告大部分都給合到一起了,但有一部分仍然排除。具體的可看一下gcc的資料。
-wl其實就是把相關的鏈結引數選項傳遞給鏈結器,如果option中包含逗號就分割一下。
其實,編譯過程中仍然會有不少的編譯選項和鏈結選項,整個gcc的文件有幾十頁,有興趣的可以看看,但不要太過沉迷進去。其實好多一般是用不到的。
1、動態庫的生成
動態庫的生成過程中有幾個棘手的問題,第乙個是c++的改名機制,在前面的文章裡提到過c++編譯器在編譯原始檔過程中會復用某種規則修改程式中的名字,這就需要鏈結器對其進行適應,一般有兩種方法,一種是建立名字修改的規則,也就是形成一種標準技術,但這有點為難。不過好在主流的編譯器廠商不多,而實際廣泛應用的也不過三兩種,這還可以忍受。另外一種就是像c編譯器一樣,復用一些關鍵字來告訴編譯器不要改名。
第二個在前面也提到過,就是靜態變數的初始化順序,也包括全域性變數,這就需要編寫時儘量減少靜態和全域性變數的使用。使用時,也盡量要集中它們到單獨的檔案中,不要滿天飛的定義全域性和靜態變數。對於c++可以讓類提供乙個單獨的init函式來處理這些定義順序,人為的控制順序。或者也可以採用某種特定的方式,通過程式來訪問,比如採用飽漢模式提前定義好全域性或者靜態物件,其它訪問都通過此物件來完成。
最後乙個是模板的問題,模板的引入本身是為了方便,但模板的靜態編譯又使其需要的條件更加嚴格一些,而模板例項化或者說特化的模式又會產生不同的**。基礎型別如int等還比較好處理。但是當遇到一些具體的物件型別時,就比較麻煩了,特別是在動態庫中,解決的方法有兩類,一類是預先使用弱符號來建立模板特化的**,編譯器生成所有的特化**和相對就把弱符號,在鏈結時如果沒有用到就拋棄。另外一類是,鏈結器在結束鏈結前都不考慮模板特化的**。只有當其它的鏈結任務都完成後,再檢查**,確實具體的模板特化的**,並復用編譯器生成,並插入到可執行檔案。
2、動態庫呼叫動態庫
動態庫可以呼叫動態庫,甚至可以形成鏈式結構,但這時候兒就需要考慮載入的順序了。實際應用動態庫的場景不外乎以下幾種:程式單獨呼叫乙個動態庫;程式單獨呼叫n個動態庫,但庫之間沒有聯絡;程式呼叫多個動態庫,動態庫之間有依賴,即有互相呼叫的現象。
3、例程
前文載入了乙個例程,基本可以直接移植過來,但這裡提供了一套額外的動態呼叫的方式:
#include int compare(int a,int b)
else
return 0;
}
動態呼叫的方式:
#include #include #include #include typedef int( * maxfunc)(int,int);
int main()
{ void * ph = null;
ph = dlopen("libcompare.so",rtld_lazy);
if (null == ph)
{std::cout<
g++ -rdynamic -o dc dycall.cpp -ldl
說明一下:
rdynamic用來通知鏈結器將所有符號新增到動態符號表中,以方便dlopen的實現向後的跟蹤,-ldl表示一定將dlib庫鏈結於此程式。
dlopen用來開啟相關的庫物件,通過對名字的判定來載入具體的動態庫。它有兩個引數,rtld_now表示在此函式呼叫完成所有必要後再定位而rtld_lazy表示在需要時再進行定位,另外還有rtld_local 和rtld_global 模式,用來對載入符號的限定
dlsym 用來將dlopen取得的物件來得到相關函式在物件檔案中的符號的位址
dlerror 返回上一次出現錯誤的字串錯誤
dlclose 關閉目標檔案
在上面的**中,會報func error,也就是說無法找到compare這個函式,為什麼呢,如果是c檔案用gcc編譯就不存在這個問題,如果用g++編譯就會有這個問題,怎麼辦呢?用nm命令看一下這個庫檔案:
$ nm libcompare.so
0000000000201030 b __bss_start
0000000000201030 b completed.7409
u __cxa_atexit@@glibc_2.2.5
w __cxa_finalize@@glibc_2.2.5
0000000000000640 t deregister_tm_clones
00000000000006b0 t __do_global_dtors_aux
0000000000200dd0 t __do_global_dtors_aux_fini_array_entry
0000000000201028 d __dso_handle
0000000000200dd8 d _dynamic
0000000000201030 d _edata
0000000000201038 b _end
0000000000000770 t _fini
00000000000006f0 t frame_dummy
0000000000200dc0 t __frame_dummy_init_array_entry
0000000000000868 r __frame_end__
0000000000201000 d _global_offset_table_
000000000000075a t _global__sub_i_compare.cpp
w __gmon_start__
000000000000077c r __gnu_eh_frame_hdr
00000000000005e0 t _init
w _itm_deregistertmclonetable
w _itm_registertmclonetable
0000000000000670 t register_tm_clones
0000000000201030 d __tmc_end__
0000000000000711 t _z41__static_initialization_and_destruction_0ii
00000000000006f5 t _z7compareii
u _znst8ios_base4initc1ev@@glibcxx_3.4
u _znst8ios_base4initd1ev@@glibcxx_3.4
0000000000000779 r _zstl19piecewise_construct
0000000000201031 b _zstl8__ioinit
後面會根據動態庫的應用如互相呼叫、除錯(檢視工具等)、呼叫靜態庫等逐步展開各種情形的學習,並對其一些內部機理進行分析,從小處一點一滴的學起,不要想著一步登頂,用侯捷的話來說「勿在浮沙築高台」。
跟我學c 中級篇 pimpl
private implementation,私有化實現。在c 中,由於語言本身的限制,沒有純粹的介面定義。這就導致了在介面的使用上很多c 的人員都是隨心而動。有用抽象類的純虛函式的,有直接用c型別的介面的。有乾脆提供介面類的 不一而足吧。根據實際情況,實事求是的選擇才是乙個好的標準。在c 中,大量...
跟我學c 中級篇 動態庫
在linux下建立動態庫的編譯選項有乙個 fpic,它的意思是與位置無關 position independet code 那麼它有什麼意義呢?在沒有出現這個選項前,是不是就不能建立動態庫呢。答案肯定是否定的。其實這就是上面提到的發展過程中的,如果沒有這個選項,只能完全載入副本,而如果使用了這個選項...
跟我學C 中級篇 STL的學習
c 的標準庫主要包含兩大類,首先是包含c的標準庫的,當然,為了適應c 對一些c庫進行了少許的修改和增加。最重要的當然是物件導向的c 庫 而c 庫又可以分成兩大類,即物件導向的c 庫和標準模板庫,也就是題目中的stl。另外在此基礎上,還要提醒同學們的是,除了上面的庫,在各個平台的開發廠商中,還會針對實...