編譯與鏈接的知識_第1頁
編譯與鏈接的知識_第2頁
編譯與鏈接的知識_第3頁
編譯與鏈接的知識_第4頁
編譯與鏈接的知識_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

1、編譯與鏈接的知識編譯與鏈接的知識2010-07-05 23:52linux下編譯hello.c程序,使用gcc hello.c,然后./a.out就可以運行;在這個簡單的命令后面隱藏了許多復(fù)雜的過程,這個過程包括了下面的步驟宏定義展開,所有的#define在這個階段都會被展開預(yù)編譯命令的處理,包括#if#ifdef一類的命令展開#include的文件,像上面hello world中的stdio.h,把stdio.h中的所有代碼合并到hello.c中去掉注釋gcc的預(yù)編譯采用的是預(yù)編譯器cpp,我們可以通過-E參數(shù)來看預(yù)編譯的結(jié)果,如:gcc-E hello.c-o hello.i生成的hell

2、o.i就是經(jīng)過了預(yù)編譯的結(jié)果在預(yù)編譯的過程中不會太多的檢查與預(yù)編譯無關(guān)的語法(#ifdef之類的還是需要檢查,#include文件路徑需要檢查),但是對于一些諸如;漏掉的語法錯誤,在這個階段都是看不出來的。寫過makefile的人都知道,我們需要加上-Ipath一系列的參數(shù)來標(biāo)示gcc對頭文件的查找路徑小提示:1.在一些程序中由于宏的原因?qū)е戮幾g錯誤,可以通過-E把宏展開再檢查錯誤,這個在編寫PHP擴展,python擴展這些大量需要使用宏的地方對于查錯誤很有幫助。2.如果在頭文件中,#include的時候帶上路徑在這個階段有時候是可以省不少事情,比如#include public/connec

3、tpool/connectpool.h,這樣在gcc的-I參數(shù)只需要指定一個路徑,不會由于不小心導(dǎo)致,文件名正好相同出現(xiàn)沖突的麻煩事情.帶路徑的方式要多寫一些代碼,也是麻煩的事情,路徑由外部指定相對也會靈活一些.編譯這個過程才是進行語法分析和詞法分析的地方,他們將我們的C/C+代碼翻譯成為匯編代碼,這也是一個編譯器最復(fù)雜的地方使用命令gcc-S hello.i-o hello.s可以看到gcc編譯出來的匯編代碼,現(xiàn)代gcc編譯器一般是把預(yù)編譯和編譯合在一起,使用cc1的程序來完成這個過程,編譯大文件的時候可以用top命令看一個cc1的進程一直在占用時間,這個時候就是程序在執(zhí)行編譯過程.后面提到

4、的編譯過程都是指cc1的處理包括了預(yù)編譯與編譯.匯編現(xiàn)在C/C+代碼已經(jīng)成為匯編代碼了,直接使用匯編代碼的編譯器把匯編變成機器碼(注意還不是可執(zhí)行的).gcc-c hello.c-o hello.o這里的hello.o就是最后的機器碼,如果作為一個靜態(tài)庫到這里可以所已經(jīng)完成了,不需要后面的過程.對于靜態(tài)庫,比如ullib,COM提供的是libullib.a,這里的.a文件其實是多個.o通過ar命令打包起來的,僅僅是為了方便使用,拋開.a直接使用.o也是一樣的小提示:1.gcc采用as進行匯編的處理過程,as由于接收的是gcc生成的標(biāo)準(zhǔn)匯編,在語法檢查上存在不少缺陷,如果是我們自己寫的匯編代碼給

5、as去處理,經(jīng)常會出現(xiàn)很多莫名奇妙的錯誤.鏈接的過程,本質(zhì)上來說是一個把所有的機器碼文件組合成一個可執(zhí)行的文件上面匯編的結(jié)果得到一個.o文件,但是這個.o要生成二執(zhí)行文件只靠它自己是不行的,它還需要一堆輔助的機器碼,幫它處理與系統(tǒng)底層打交道的事情.gcc-o hello hello.o這樣就把一個.o文件鏈接成為了一個二進制可執(zhí)行文件.這個地方也是本文討論的重點,在后面會有更詳細(xì)的說明小提示:有些程序在編譯的時候會出現(xiàn)linker input because linking not done的提示(雖然gcc不認(rèn)為是錯誤,這個提示還是會出現(xiàn)的),這里就是把編譯和鏈接使用的參數(shù)搞混了,比如g+-

6、c test.cpp-I././ullib/include-L././ullib/lib/-lullib這樣的寫法就會導(dǎo)致上面的提示,因為在編譯的過程中是不需要鏈接的,它們兩個過程其實是獨立的靜態(tài)鏈接鏈接的過程這里先介紹一下,鏈接器所做的工作其實鏈接做的工作分兩塊:符號解析和重定位符號解析符號包括了我們的程序中的被定義和引用的函數(shù)和變量信息在命令行上使用nm./test test是用戶的二進制程序,包括可以把在二進制目標(biāo)文件中符號表輸出009 b8 A_bss_start 004 cc tcall_gmon_start 009 b8 bcompleted.1 00788 d_CTOR_END_

7、 00780 d_CTOR_LIST_ 009 a0 D_data_start 009 a0 Wdata_start 00630 t_do_global_ctors_aux 004 f0 t_do_global_dtors_aux 009 a8 D_dso_handle 00798 d_DTOR_END_ 00790 d_DTOR_LIST_ 007 a8 D_DYNAMIC 009 b8 A_edata 009 c0 A_end 00668 T_fini 00780 A_fini_array_end 00780 A_fini_array_start 00530 tframe_dummy 00

8、778 r_FRAME_END_ 00970 D_GLOBAL_OFFSET_TABLE_ w_gmon_start_ 00448 T_init 00780 A_init_array_end當(dāng)然上面由nm輸出的符號表可以通過編譯命令去除,讓人不能直接看到。鏈接器解析符號引用的方式是將每一個引用的符號與其它的目標(biāo)文件(.o)的符號表中一個符號的定義聯(lián)系起來,對于那些和引用定義在相同模塊的本地符號(注:static修飾的),編譯器在編譯期就可以發(fā)現(xiàn)問題,但是對于那些全局的符號引用就比較麻煩了.下面來看一個最簡單程序:#include stdio.h int foo();int main()foo(

9、);return 0;我們把文件命名為test.cpp,采用下面的方式進行編譯g+-c test.cpp g+-o test test.o第一步正常結(jié)束,并且生成了test.o文件,到第二步的時候報了如下的錯誤test.o(.text+0x5):In functionmain:undefined reference tofoo()collect2:ld returned 1exit status由于foo是全局符號,在編譯的時候不會報錯,等到鏈接的時候,發(fā)現(xiàn)沒有找到對應(yīng)的符號,就會報出上面的錯誤。但是如果我們把上面的寫法改成下面這樣#include stdio.h/注意這里的static st

10、atic int foo();int main()foo();return 0;在運行g(shù)+-c test.cpp,馬上就報出下面的錯誤:test.cpp:19:error:int foo()used but never defined在編譯器就發(fā)現(xiàn)foo無法生成目標(biāo)文件的符號表,可以馬上報錯,對于一些本地使用的函數(shù)使用static一方面可以避免符號污染,另一方面也可以讓編譯器盡快的發(fā)現(xiàn)錯誤.在基礎(chǔ)庫中提供的都是一系列的.a文件,這些.a文件其實是一批的目標(biāo)文件(.o)的打包結(jié)果.這樣的目的是可以方便的使用已有代碼生成的結(jié)果,一般情況下是一個.c/.cpp文件生成一個.o文件,在編譯的時候如果帶

11、上一堆的.o文件顯的很不方便,像:g+-o main main.cpp a.o b.o c.o這樣大量的使用.o也很容易出錯,在linux下使用archive來講這些.o存檔和打包.所以我們就可以把編譯參數(shù)寫成g+-o main main.cpp./libullib.a我們可以使用./libullib.a直接使用libullib.a這個庫,不過gcc提供了另外的方式來使用:g+-o main main.cpp-L./-lullib-L指定需要查找的庫文件的路徑,-l選擇需要使用的庫名字,不過庫的名字需要用lib+name的方式命名,才會被gcc認(rèn)出來.不過上面的這種方式存在一個問題就是不區(qū)分動

12、態(tài)庫和靜態(tài)庫,這個問題在后面介紹動態(tài)庫的時候還會提到.當(dāng)存在多個.a,并且在庫之間也存在依賴關(guān)系,這個時候情況就比較復(fù)雜.如果要使用lib2-64/dict,dict又依賴ullib,這個時候需要寫成類似下面的形式g+-o main main.cpp-L./lib2-64/dict/lib-L./lib2-64/ullib/lib-ldict-lullib-lullib需要寫在-ldict的后面,這是由于在默認(rèn)情況對于符號表的解析和查找工作是由后往前(內(nèi)部實現(xiàn)是一個類似堆棧的尾遞歸).所以當(dāng)所使用的庫本身存在依賴關(guān)系的時候,越是基礎(chǔ)的庫就越是需要放到后面.否則如果上面把-ldict-lulib

13、的位置換一下,可能就會出現(xiàn)undefined reference to xxx的錯誤.當(dāng)然gcc提供了另外的方式的來解決這個問題g+-o main main.cpp-L./lib2-64/dict/lib-L./l ib2-64/ullib/lib-Xlinker-(-ldict-lullib-Xlinker-)可以看到我們需要的庫被-Xlinker-(和-Xlinker-)包含起來,gcc在這里處理的時候會循環(huán)自動查找依賴關(guān)系,不過這樣的代價就是延長gcc的編譯時間,如果使用的庫非常的多時候,對編譯的耗時影響還是非常大.-Xlinker有時候也簡寫成-Wl,,它的意思是它后面的參數(shù)是給鏈接器

14、使用的.-Xlinker和-Wl的區(qū)別是一個后面跟的參數(shù)是用空格,另一個是用,我們通過nm命令查看目標(biāo)文件,可以看到類似下面的結(jié)果109740 T_Z11ds_syn_loadPcS_ 209 c62 T_Z11ds_syn_seekP16Sdict_search_synPcS1_i 307928 T_Z11dsur_searchPcS_S_ 4&nbs p;U _Z11ul_read 5&nbs p;U _Z11ul_writelogiPKcz 6000 a2 T_Z12creat_sign32Pc其中用U標(biāo)示的符號_Z11ul_read(其實是ullib中的ul_readfile),表示在

15、dict的目標(biāo)文件中沒有找到ul_readfile函數(shù).在鏈接的時候,鏈接器就會去其他的目標(biāo)文件中查找_Z11ul_read的符號小提示:編譯的時候采用-Lxxx-lyyy的形式使用庫,-L和-l這個參數(shù)并沒有配對的關(guān)系,我們的一些Makefile為了維護方便把他們寫成配對的形式,造成了誤解.其實完全可以寫成-Lpath1,-Lpath2,-Lpath3,-llib1這樣的形式.在具體鏈接的時候,gcc是以.o文件為單位,編譯的時候如果寫g+-o main main.cpp libx.o那么無論main.cpp中是否使用到libx.o,libx.o中的所有符號都會被載入到mian函數(shù)中.但是如

16、果是針對.a,寫成g+-o main main.cpp-L./-lx,這個時候gcc在鏈接的時候只會鏈接有被用到.o,如果出現(xiàn)libx.a中的某個.o文件中沒有任何一個符號被main用到,那么這個.o就不會被鏈接到main中重定位經(jīng)過上面的符號解析后,所有的符號都可以找到它所對應(yīng)的實際位置(U表示的鏈接找到具體的符號位置).as匯編生成一個目標(biāo)模塊的時候,它不知道數(shù)據(jù)和代碼在最后具體的位置,同時也不知道任何外部定義的符號的具體位置,所以as在生成目標(biāo)代碼的時候,對于位置未知的符號,它會生成一個重定位表目,告訴鏈接器在將目標(biāo)文件合并成可執(zhí)行文件時候如何修改地址成最終的位置g+和gcc采用gcc和

17、g+在編譯的時候產(chǎn)生的符號有所不同.在C+中由于要支持函數(shù)重載,命名空間等特性,g+會把函數(shù)+參數(shù)(可能還有命名空間),把函數(shù)命變成一個特殊并且唯一的符號名.例如:int foo(int a);在gcc編譯后,在符號表中的名字就是函數(shù)名foo,但是在g+編譯后名字可能就變成了_Z3fooi,我們可以使用c+filt命令把一個符號還原成它原本的樣子,比如c+filt _Z3fooi運行的結(jié)果可以得到foo(int)由于在C+和純C環(huán)境中,符號表存在不兼容問題,C程序不能直接調(diào)用C+編譯出來的庫,C+程序也不能直接調(diào)用C編譯出來的庫.為了解決這個問題C+中引入了externC的方式.externC

18、int foo(int a);這樣在用g+編譯的時候,c+的編譯器會自動把上面的int foo(int a)當(dāng)做C的接口進行符號轉(zhuǎn)化.這樣在純C里面就可以認(rèn)出這些符號.不過這里存在一個問題,externC是C+支持的,gcc并不認(rèn)識,所有在實際中一般采用下面的方式使用+#ifdef _cplusplus externC#endif int foo(int a);#ifdef _cplusplus#endif這樣這個頭文件中的接口即可以給gcc使用也可以給g+使用,當(dāng)然在externC中的接口是不支持重載,默認(rèn)參數(shù)等特性在我們的64位編譯環(huán)境中如果有g(shù)cc的程序使用上面方式g+編譯出來的庫,需要

19、加上-lstdc+,這是因為,對于我們64位環(huán)境下g+編譯出來的庫,需要使用到一個_gxx_personality_v0的符號,它所在的位置是/usr/lib64/libstdc+.so.6(C+的標(biāo)準(zhǔn)庫iostream都在里面,C+程序都需要的).但是在32位2.96 g+編譯器中是不需要_gxx_personality_v0,所有編譯可以不加上-lstdc+小提示:在linux gcc中,只有在源代碼使用.c做后綴,并且使用gcc編譯才會被編譯成純C的結(jié)果,其他情況像g+編譯.c文件,或者gcc編譯.cc,.cpp文件都會被當(dāng)作C+程序編譯成C+的目標(biāo)文件,gcc和g+唯一的不同在于gcc

20、不會主動鏈接-lstdc+在externC中如果存在默認(rèn)參數(shù)的接口,在g+編譯的時候不會出現(xiàn)問題,但是gcc使用的時候會報錯.因為對于函數(shù)重載,接口的符號表還是和不用默認(rèn)參數(shù)的時候是一樣的.符號表沖突編譯程序的時候時常會遇到類似于multiple definition offoo()的錯誤.這些錯誤的產(chǎn)生都是由于所使用的.o文件中存在了相同的符號造成的.比如:libx.cpp int foo()return 30;liby.cpp int foo()return 20;將libx.cpp,liby.cpp編譯成libx.o和liby.o兩個文件g+-o main main.cpp libx.o

21、 liby.o這個時候就會報出multiple definition offoo()的錯誤(一些參數(shù)可以把這個警報關(guān)掉)但是如果把libx.o和liby.o分別打包成libx.a和liby.a用下面的方式編譯g+-o main main.cpp-L./-lx-ly這個時候編譯不會報錯,它會選擇第一個出現(xiàn)的庫,上面的例子中會選擇libx中的foo可以通過g+-o main main.cpp-L./-lx-ly-Wl,-trace-symbol=_Z3foov的命令查看符號具體是鏈接到哪個庫中,g+-o main main.cpp-L./-lx-ly-Wl,-cref可以把所有的符號鏈接都輸出(無

22、論是否最后被使用)小提示:對于一些定義在頭文件中的全局常量,gcc和g+有不同的行為,g+中const也同時是static的,但gcc不是例如:foo.h中存在一個const int INTVALUE=2000;的全局常量有兩個庫a和b,他們在生成的時候有使用到了INTVALUE,如果有一個程序main同時使用到了a庫和b庫,在鏈接的時候gcc編譯的結(jié)果就會報錯,但如果a和b都是g+編譯的話結(jié)果卻一切正常.這個原因主要是在g+中會把INTVALUE這種const常量當(dāng)做static的,這樣就是一個局部變量,不會導(dǎo)致沖突,但是如果是gcc編譯的話,這個地方INTVALUE會被認(rèn)為是一個對外的全局

23、常量是非static的,這個時候就會造成鏈接錯誤動態(tài)鏈接對于靜態(tài)庫的使用,有下面兩個問題當(dāng)我們需要對某一個庫進行更新的時候,我們必須把一個可執(zhí)行文件再完整的進行一些重新編譯在程序運行的時候代碼是會被載入機器的內(nèi)存中,如果采用靜態(tài)庫就會出現(xiàn)一個庫需要被copy到多個內(nèi)存程序中,這個一方面占用了一定的內(nèi)存,另一方面對于CPU的cache不夠友好鏈接的控制,從前面的介紹中可以看到靜態(tài)庫的連接行為我們不好控制,做不到在運行期替換使用的庫編譯后的程序就是二進制代碼,有些代碼它們涉及到不同的機器和環(huán)境,假設(shè)在A機器上編譯了一個程序X,把它直接放到B機器上去運行,由于A和B環(huán)境存在差異,直接運行X程序可能存

24、在問題,這個時候如果把和機器相關(guān)的這部分做成動態(tài)庫C,并且保證接口一致,編譯X程序的時候只調(diào)用C的對外接口.對于一般的用戶態(tài)的X程序而言,就可以簡單的從A環(huán)境放到B環(huán)境中.但如果是靜態(tài)編譯,就可能做不到這點,需要在B機器上重新編譯一次.動態(tài)鏈接庫在linux被稱為共享庫(shared library,下文提到的共享庫和動態(tài)鏈接庫都是指代shared library),它主要是為了解決上面列出靜態(tài)庫的缺點而提出的.。共享庫的使用共享庫的使用主要有兩種方式,一種方式和.a的靜態(tài)庫類似由編譯器來控制,其實質(zhì)和二進制程序一樣都是由系統(tǒng)中的載入器(ld-linux.so)載入,另一種是寫在代碼中,由我們

25、自己的代碼來控制.還是以前面的例子為例:g+-shared-fPIC-o libx.so libx.cpp編譯的時候和靜態(tài)庫類似,只是加上了-shared和-fPIC,將輸出命名改為.so然后和可執(zhí)行文件鏈接.a一樣,都是g+-o main main.cpp-L./-lx這樣main就是調(diào)用libx.so,在運行的時候可能會出現(xiàn)找不到libx.so的錯誤,這個原因是由于動態(tài)的庫查找路徑的問題,動態(tài)庫默認(rèn)的查找路徑是由/etc/ld.so.conf文件來指定,在運行可執(zhí)行文件的時候,按照順會去這些目錄下查找需要的共享庫。我們可以通過環(huán)境變量LD_LIBRARY_PATH來指定共享庫的查找路徑(注

26、:LD_LIBRARY_PATH的優(yōu)先級比ld.so.conf要高).命令上運行l(wèi)dd./main我們可以看到這個二進制程序在運行的時候需要使用的動態(tài)庫,例如:libx.so=/home/bnh/tmp/test/libx.so(0x003cb000)libstdc+.so.6=/usr/lib/libstdc+.so.6(0x 00702000)libm.so.6=/lib/tls/libm.so.6(0x00bde000)libgcc_s.so.1=/lib/libgcc_s.so.1(0x00c3e000)libc.so.6=/lib/tls/libc.so.6(0x00aab000)這

27、里列出了mian所需要的動態(tài)庫,如果有看類似libx.so=no found的錯誤,就意味著路徑不對,需要設(shè)置LD_LIBRARY_PATH來指定路徑手動載入共享庫除了采用類型于靜態(tài)庫的方式來使用動態(tài)庫,我們還可以通過由代碼來控制動態(tài)庫的使用。這種方式允許應(yīng)用程序在運行時加載和鏈接共享庫,主要有下面的四個接口載入動態(tài)鏈接庫void*dlopen(const char* flag);獲取動態(tài)庫中的符號void*dlsym(void*handle,char*symbol);關(guān)閉動態(tài)鏈接庫void dlclose(void*handle);輸出錯誤信息const char*dlerror(void)

28、;看下面的例子:typedef int foo_t();foo_t*foo=(foo_t*)dlsym(handle,foo);通過上面的方式我們可以載入符號foo所對應(yīng)的地址,然后通過強制類型轉(zhuǎn)換給一個函數(shù)指針,當(dāng)然這里函數(shù)指針的類型需要和符號的原型類型保持一致,這些一般是由共享庫所對應(yīng)的頭文件提供.這里要注意一個問題,在dlsym中載入的符號表示是和我們使用nm庫文件所看到符號表要保持一致,這里就有一個前面提到的gcc和g+符號表的不同,一個int foo(),如果是g+編譯,并且沒有externC導(dǎo)出接口,那么用dlsym載入的時候需要用dlsym(handle,_Z3foov)方式才可

29、以載入函數(shù)int foo(),所以建議所以的共享庫對外接口都采用externC的方式導(dǎo)出純C接口對外使用,這樣在使用上也會比較方便dlopen的flag標(biāo)志可以選擇RTLD_GLOBAL,RTLD_NOW,RTLD_LAZY.RTLD_NOW,RTLD_LAZY只是表示載入的符號是一開始就被載入還等到使用的時候被載入,對于多數(shù)應(yīng)用而言沒有什么特別的影響.這兩個標(biāo)志都可以通過|和RTLD_GLOBAL一起連用這里主要是說明RTLD_GLOBAL的功能,考慮這樣的一個情況:我們有一個main.cpp,調(diào)用了兩個動態(tài)libA,和libB,假設(shè)A中有一個對外接口叫做testA,在main.cpp可以通過dlsym獲取到testA的指針,進行使用.但是對于libB中的接口,它是看到不libA的接口,使用testA是不能調(diào)用到libA中的testA的,但是如果在dlopen打開libA.so的時候,設(shè)置了RTLD_GLOBAL這個選項,就可以把libA.so中的接口升級為全局可見,這樣在libB中就可以直接調(diào)用libA中的te

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論