用tcc將程序編譯為.obj文件。
這裡也可以使用tcc -linclude run.c來將run.c文件編譯成run.obj文件。
再用tcc對下面的程序進行編譯鏈接,發現提示錯誤:
提示標志f在程序中未定義。這說明如果程序裡出現未定義的變量或函數,編譯器能夠正常將原文件編譯成.obj文件,只是會提示而已。
要怎麼生成正確的exe文件呢,我們要把run1.c中未定義的f函數鏈接進來,但是我們之前的鏈接都是鏈接的系統提供的相關文件,怎麼鏈接自帶文件呢?我們先研究一下tlib.exe。
tlib.exe使用方法如下;
即使用tlib的格式為tlib libname [/C] [/E] commands, listfile
libname:要建立的用戶目標模塊庫,缺省的擴展名為.LIB
/C:大小寫敏感標志。該選項不常用。
/E:建立擴展字典。建立擴展字典可以加速大的庫文件的連接過程。
commands: 操作列表,由若干個動作符以及每個動作符後面的文件名或模塊名組成。TILB支持的動作符有5種:“+"、“-"、“*"、“-*"或“*-"、“-+"或“+-"。“+"是把指定的文件加到指定的庫中; “-"從庫中刪除指定的模塊;“*"將相應的模塊從庫中抽取並寫到指定的文件中,原庫不變;“-*"或“*-"是將庫中指定的模塊拷貝到指定的文件中,然後把該模塊從庫中刪除;“-+"或“+-"是將指定的模塊用指定的文件或模塊代替。
Listfile:建立列表文件。列表文件按字母順序將庫中各模塊列表,為文本文件,可用DOS的TYPE命令查看。
那麼將含有f函數的run.obj添加到cs.lib裡的語句如下:
之後用tcc編譯鏈接文件run1.c,沒有出現錯誤提示。用debug加載生成的exe文件:
這是main函數的代碼
這是f函數的代碼
所以,我們將函數f寫在程序run.c中,編譯成run.obj,再將run.obj用tlib鏈接入cs.lib,這樣tcc編譯時發現原文件中沒有定義函數f,就會在c語言默認的函數庫cs.lib中尋找,找到後將其鏈接,生成run1.exe文件,所以我們在run1.exe文件中可以看到函數f的代碼,它是在tlink連接時加入的。
用tlib cs.lib cs.txt可以在cs.txt或者cs.lst文件中導入cs.lib的函數的目錄。
將此程序編譯成f.obj,並加入cs.lib中。
將上面的程序編譯鏈接成b.exe,用debug加載:
Main函數的內容為:
f1函數的內容為:
f2函數的內容為:
函數func的內容為;
程序b.c中並沒有寫f1、f2和printf函數,這些函數的代碼同樣是在連接的時候加入的。
b.exe中有f3的代碼,因為f3和f1、f2一起被加入了cs.lib中,而cs.lib被連接入了b.exe,所以b.exe含有它的全部函數,只是main函數中只調用了f1、f2、printf函數而已。
函數f3的代碼緊接著函數f2的代碼,地址為1056.
那麼有沒有一種方案,使得在編譯連接時能夠動態地裝入函數的代碼,而不是將庫函數全部裝入exe文件呢?
我們看題目要求的是f.c中的三個函數要裝入cs.lib,在編譯連接時動態載入代碼。但是如果我們用tcc原來的連接方式,就會把cs.lib整個載入代碼中。那應該怎麼樣才不會出現這種情況呢?我覺得應該是改變tlink的連接方式,查看tlink連接選項:
我用tcc將b.c編譯成obj文件,再用tlink b.obj/n進行連接,結果出現如下錯誤:
很顯然是沒有連接cs.lib所致,但是為什麼先用tcc編譯再用tlink連接會出錯呢?
經過查找資料和實驗,tlink連接obj文件生成exe文件的正確指令如下:
第一個意思是用小模式連接b.obj,第二個是指生成的目標文件是b.exe,第三個是指沒有使用到映像文件,第四個是指連接需要用到的庫文件有cs.lib、emu.lib、maths.lib。
函數f3包含在f.obj裡,而後者被載入了cs.lib文件,cs.lib在連接時被載入b.exe文件,那麼是不是cs.lib裡的所有函數都載入了b.exe文件呢?查找資料有這麼一段話:
不會。當啟動連接程序時,它會尋找“未定義的外部函數”,也就是說,它將在每一個庫文件中查找源代碼文件中未定義的函數。當它找到一個未定義的外部函數後,它會引入包含該函數定義的目標代碼(obj)。不幸的是,如果這個函數是在一個包含其它函數定義的源文件中被編譯的話,那麼這些函數也會被包含進來,你的可執行代碼中將包含一些不需要的代碼。因此,將庫函數放到各自的源文件中是很重要的——否則會浪費寶貴的程序空間。有些編譯程序包含特殊的“精明的”連接程序,這些連接程序能查出不需要的函數並去掉它們,從而使這些函數不再進入你的程序。
很顯然tlink並不是上面資料裡所說的“精明的”連接程序,它會將和源代碼中未定義函數一起編譯的所有函數都載入exe文件中,所以f3會被載入b.exe中。
那麼我們的問題就很好解決了,我們可以將f1、f2函數寫在一個程序裡編譯成obj文件,將f3寫在一個程序裡編譯成obj文件,再將這兩個文件加入cs.lib中,然後進行下面的連接,b.exe文件中就不會出現f3函數的代碼了。實驗結果發現,原來緊跟在f2的代碼後面的f3的代碼現在沒有了:
所以其實是我之前的思路重點錯了,只覺得函數f1、f2、f3都在cs.lib裡面不管怎麼放都是一樣的,但是其實它們編譯的方法不同,在cs.lib裡面存放的位置或機制也應該不同,我們思考時,應該更加全面地想問題。
用下面的函數替換cs.lib裡的printf函數:
將函數編譯成obj文件,再用如下語句替換:
這樣再使用printf函數就會輸出“Do you want to use printf?No printf here.”
如下圖程序:
在正常情況下應該輸出3,但是用更改後的cs.lib連接後,輸出結果如下:
這時printf函數是一個不接受參數、只輸出固定語句的函數。
1、我們在本章研究裡是把要添加的obj文件插入cs.lib中再連接,那麼能否自己建立lib庫,並使tcc編譯時對它進行連接呢?
2、我們知道cs.lib裡在被連接時是將源文件中未被定義的函數及其一起編譯的函數全部加入生成的exe文件中,那麼cs.lib裡的其他文件是怎麼編譯的?都是單獨編譯的嗎?
3、Printf和put函數有什麼區別?
4、obj文件給出的是偏移地址,exe文件給出了段地址和偏移地址?
5、include頭文件在預編譯過程中把其他文件合成一個文件。嘗試#include<f.c>也是可以正確運行的,看匯編代碼這種和加入cs.lib有什麼不一樣。
6、加入obj文件後之後cs.lib的大小減小了,這是為什麼?
本章研究了obj文件的連接問題,掌握了將obj文件加入cs.lib從而連接進文件的方法,熟悉了tlib.exe的基本用法。