?? unix系統開發-鏈接處理(1).txt
字號:
UNIX系統開發-鏈接處理(1)
我們已經知道鏈接實際上是指將在一個模塊中引用的符號與它在另一個模塊中的定義相鏈接的過程。并且我們還知道鏈接分為動態鏈接和靜態鏈接兩種方式。不論是對哪一種方式。鏈接程序都將搜索程序中的每一個模塊,包括所用到的每一個庫文件,以在這些文件中尋找在某個模塊中沒有定義的外部符號的定義。如果沒有找到某個被引用的符號的定義,鏈接程序將報告錯誤。此時可執行文件的創建將會失敗。
對于靜態鏈接和動態鏈接,其區別主要在于搜索到某個符號的定義后鏈接程序所做的不同工作:
對靜態鏈接,鏈接程序將把靜態鏈接庫(檔案庫)中哪些被用戶程序所引用的符號定義的目標代碼,拷貝到最終生成的可執行文件中。這種情況下,程序中的外部符號引用同其定義的鏈接是在可執行文件被建立的時候完成的。
對動態鏈接,共享對象(動態鏈接庫)中的內容在運行時被映射到用戶進程的虛地址空間。鏈接程序所作的僅僅是在最終生成的可執行文件中記錄下到哪里去找外部符號定義的目標代碼。這種情況下符號的外部引用與其定義的鏈接是在程序運行時完成的。
在這一節中,我們將詳細地討論鏈接過程。如編譯系統的一些缺省設置、用戶如何生成自己的動態庫或靜態庫、如何在程序中鏈接這些庫文件,以及動態鏈接庫是如何實現的,等等問題。在掌握了這些內容之后,讀者將能夠高效地組織自己的源文件,提高開發的效率和程序的可維護性。
缺省設置
前面一節中,我們使用:
$ cc -o myprg myprog.c myfunc.c
命令來生成可執行文件。此時cc將生成同每一個C源程序對應的目標文件,并把它們彼此鏈接,以生成一個可執行的程序文件。對于所生成的每一個目標文件,我們稱之為可重定位的目標文件,因為這些目標文件中含有尚未同其定義相鏈接的符號引用,也即尚沒有在內存中分配地址。
但我們可以注意到,myprog.c中所調用的printf()和myfunc.c中所調用的isdigit(),這兩個函數是我們在自己的程序中所沒有定義的。這兩個函數是標準C庫所提供的。缺省情況下鏈接程序將自動地道標準C庫中去查找哪些被用戶程序所調用的函數的定義。
標準的C函數庫有動態和靜態兩個不同的版本,其文件名分別是libc.so和libc.a,分別用于動態鏈接和靜態鏈接。缺省情況下,鏈接程序將對標準C庫函數調用進行動態鏈接(使用libc.so),也即所調用的函數將在運行時與程序相鏈接。但有些標準函數由于設計上的一些遺漏,在libc.so中并沒有其定義。對這些函數鏈接程序將使用libc.a進行靜態鏈接,也即將這些函數的代碼拷貝至可執行文件中。但對哪些既可以靜態鏈接,又可以進行動態鏈接的符號引用,程序員可以自主選擇到底使用哪種鏈接方式。后面我們將介紹如何完成這一點。
標準C函數庫中所包含的函數有如下幾類:
標準I/O函數,如fopen,fread,fwrite,fclose等;
字符串操作函數,如strcat,strcmp,strcpy等;
對八位字符編碼的整數值分類的函數,如isalpha,isupper,islower,isdigit等;
字符、整數或字符串轉換函數,如atoi,atol,strtoul,atof等;
用庫函數形式實現的系統調用,如open,read,write,close等等。
關于其他未列出的函數及據說明讀者可參考有關的手冊。
cc自動對標準C庫的動態鏈接是下述約定為基礎的:
根據約定,共享對象或動態鏈接庫的名稱,前綴是lib而后綴是.so;檔案庫或者靜態鏈接庫,前綴為lib而后綴是.a。因此標準C庫的共享版本的名稱將是libc.so;而靜態版本的名稱則是libc.a。
cc命令的-l選項能夠識別上述約定。也就是說:
$ cc ... -lx
將使鏈接程序搜索動態庫libx.so或靜態庫libx.a。缺省情況下,cc將自動地把-lc選項傳給鏈接程序。因此,從實際看-l選項是一個鏈接程序選項。
缺省情況下,鏈接程序將優先查找同一目錄下的動態庫版本libx.so,不成功時才選擇靜態庫libx.a。
缺省情況下,鏈接程序將在系統的標準位置(/usr/ccs/lib和/usr/lib目錄下)按上述順序搜索所需的庫。由編譯系統提供的標準庫一般被放在/usr/ccs/lib目錄下。
在這些約定的基礎上,我們可以更明確地講,缺省的cc命令行將引導鏈接程序搜索/usr/ccs/lib/libc.so,而不是它所對應的靜態庫。后面我們將要講到如何將用戶的程序同某個庫的靜態版本相鏈接(如同libc.a相鏈接二不是同libc.so相鏈接),以及用戶如何建立起自己的靜態庫或動態庫,并使程序與這些庫相鏈接。當然,如果缺省的cc命令行能夠滿足編譯要求的話,那么就用不著進行多余的鏈接處理了。
標準庫函數的鏈接
libc.so只是一個目標文件,其中包含有標準C庫中每一個函數的代碼。當某個程序調用該庫中的某個函數并且動態地將自己的程序相鏈接時,libc.so的全部內容將被映射到運行時與該程序相對應的進程虛地址空間中。
對于靜態庫(檔案庫)則不是這么回事,每一個函數或者一組相關的函數代碼被保存到它們自己的目標文件中。然后這些目標文件被收集在一個檔案庫中。但程序員在命令行中指定對標準C庫進行靜態鏈接的時候,鏈接程序將在檔案庫中搜索被調用函數的代碼并將其拷貝到最終的可執行文件中。這里我們看到使用靜態鏈接時最終可執行文件中只包含有哪些所需的代碼。
那么如何使鏈接程序改變缺省的動態鏈接方式而進行靜態鏈接呢?方法是在cc命令行中加上-dn選項,如下所示:
$ cc -o myprog -dn myprog.c myfunc.c
這樣對于myprog.c和myfunc.c中的標準C庫函數調用,鏈接程序將在libc.a中去搜索目標代碼并且將其拷貝到最終可執行的文件中。
在程序比較復雜的情況下,一個程序可能就不是僅僅調用了標準C庫中的函數了。例如,對于用到了數學運算sin(),cos()這類函數的程序,它就可能需要同數學函數庫鏈接。由于編譯系統只提供了libc和libdl(對動態鏈接進行控制的函數調用集合)的動態版本,因此,除非是用戶自己在標準位置安裝了數學函數庫的動態版本libm.so,那么鏈接程序將在標準位置查找libm.a庫函數。當然這需要在cc命令行中加上一個-l選項,如下所示:
$ cc file.c file2.c
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -