在c:\下建立文件夾c,並將編譯器tcc.exe、連接器tlink.exe、相關文件c0s.obj、cs.lib、emu.lib、maths.lib放入文件夾中。
要搭建一個簡單的C語言編譯環境,需要TC2.0、c0s.obj、emu.lib、maths.lib、graphics.lib、cs.lib文件。而這裡用編譯器tcc.exe、連接器tlink.exe代替了TC2.0,而且相關文件也少了graphics.lib,為什麼這樣也可以呢?我們先嘗試在新建立的環境下編譯連接一個文件:
用命令“tcc hello.c”編譯連接生成文件hello.obj和hello.exe:
運行結果為:
在編譯連接過程中,第一次我用tcc hello.c編譯,發現只生成了hello.obj文件,然後我又用tlink hello.obj連接生成hello.exe文件,結果發現運行這個exe文件會出錯。經過檢查後發現,我把相關文件裡的maths.lib錯誤地復制成了mathl.lib,可能在連接文件時發生了錯誤,導致exe文件出錯。
那麼這個環境與之前的環境相比,少了TC2.0、graphics.lib,多了tcc.exe、tlink.exe。在網上查閱資料發現graphics.lib是一個c語言圖形庫,TC2.0連接需要這個而tcc.exe不需要,我們可以理解為tc2.0在tcc.exe的基礎上多了這麼一個擴展,每一個庫文件都相當於一個小模塊,支持一種擴展。我們需要的時候,只需要添加相應的庫文件就行了。
從功能上來看,tcc.exe只能從cmd編譯當前目錄下已存在的文件,而TC2.0則支持文件的創建、修改、保存、編譯、連接,是集成了tcc.exe和tlink.exe的一個c語言小型開發平台。
那麼既然新建立的環境將所需要的相關文件減少到了4個,那麼是不是還可以減少呢?經過實驗,我發現不論去掉哪一種相關文件,在編譯時都會出錯,生成一個錯誤的hello.obj文件,並且cmd會有相似的提示:
所以我們可以斷定,在該環境下,4個相關文件必須都在才能保證編譯的成功。
補充研究:在網上查詢資料發現,TC2.0是一個集成的開發環境,它集成了以下文件:
INSTALL.EXE 安裝程序文件
TC.EXE 集成編譯
TCINST.EXE 集成開發環境的配置設置程序
TCHELP.TCH 幫助文件
THELP.COM 讀取
TCHELP.TCH的駐留程序
README 關於Turbo C的信息文件
TCCONFIG.EXE 配置文件轉換程序
MAKE.EXE 項目管理工具
TCC.EXE 命令行編譯
TLINK.EXE Turbo C系列連接器
TLIB.EXE Turbo C系列庫管理工具
C0?.OBJ 不同模式啟動代碼
C?.LIB 不同模式運行庫
GRAPHICS.LIB 圖形庫
EMU.LIB 8087仿真庫
FP87.LIB 8087庫
*.H Turbo C頭文件
*.BGI 不同顯示器圖形驅動程序
*.C Turbo C例行程序(源文件)
其中: 上面的?分別為:
T Tiny(微型模式)
S Small(小模式)
C Compact(緊湊模式)
M Medium(中型模式)
L Large(大模式)
H Huge(巨大模式)
這個在TC的安裝文件夾裡其實也可以看出來:
但是我們之前的研究發現只需要需要TC2.0就可以成功編譯文件,而不需要tcc.exe也在目錄下,所以TC2.0是把tcc.exe集成在自己的文件內部的,而不是對外部的編譯器進行調用。
查看tcc.exe的功能:
可以看到tcc的命令格式是:tcc [選項] [文件名]
缺少正確的相關文件會對編譯造成怎樣的影響呢?我先用完整的相關文件進行編譯,生成的exe文件有8kb,而刪去cs.lib後進行編譯,發現生成的exe文件只有536字節,比正確的文件要小得多,所以我認為在用tlink進行連接時因為相關文件的缺失導致有很大一部分相關文件都沒有連接進來。
在網上查閱資料發現啟動代碼有T Tiny(微型模式) 、S Small(小模式)、C Compact(緊湊模式) 、M Medium(中型模式) 、L Large(大模式) 、H Huge(巨大模式),那麼分別對應的相關文件應該為:c0t.obj,ct.lib,c0s.obj,cs.lib...果然,我們在TC2.0的lib庫裡發現了相關文件;
我發現,其他的模式都有對應的c0*.obj和c*.lib文件,而微型模式只有c0t.obj文件,這是為什麼呢?是該模式的編譯特性決定他們只要用一個文件嗎?我們來試試是否可以編譯成功,將c0t.obj拷貝到c:\c文件夾下,再用tcc -mt hello.c編譯,發現可以編譯連接成功,生成的exe文件也能成功運行,所以微型模式的編譯是不需要特定的lib文件,這說明微型模式在編譯時不需要向文件裡面加入專門的庫函數。
要實現打印出子函數的段地址和偏移地址,首先要知道,子函數的段地址和偏移地址放在哪裡。我們在《20140426_綜合研究2研究報告》中發現:(1)函數的名字就代表它的偏移地址。(2)函數的調用在匯編裡是采用call-ret方法實現的。另外我們知道匯編中存儲當前段地址的寄存器為CS寄存器。
首先填充main函數,分別打印每一個函數的偏移地址,以及運行函數後CS寄存器的值:
顯示結果為:
發現main函數的偏移地址為21b,f1的偏移地址為1fa,f2的偏移地址為205,f3的偏移地址為210,cs寄存器的一直為1a2.那麼CS裡的值真的是子函數的段地址的值嗎?我們用debug加載程序:
發現main()、f1()、f2()、f3()的偏移地址是對的。但是段地址應為076a而不是打印出來的CS寄存器的值1a2。
如果用長整形將main函數和f1的值全部打印出來:
則段地址還是1a2。但是用debug查看並不一樣。那麼問題在哪裡呢?
在網上查看,發現有一個相似問題的解釋是這樣的:“調試的情況下 是用調試器來實對 單步 斷點異常的處理。加載了更多的函數。 當然地址會不一樣了。”
我覺得可能是直接運行和debug調試分配的內存空間不一樣,如上所說,debug調試要加載更多的函數,所以main函數的段地址會相對較大。
但是這裡是子函數和main函數在一個段裡,所以子函數的段地址可以在主函數裡用_CS
表示,如果子函數和main函數不在一個段裡呢?我們知道:用tcc hello.c生成的文件可有兩個段,一個為代碼段,一個為棧和數據段。所以��函數和主函數都要在同一個代碼段裡。那麼如果代碼量超過64kb,一個段存放不下,怎麼辦?在網上查閱資料如下:
C 語言中提供了6種編譯模式,這6種模式是:
微模式(Tiny),小模式(Small),中模式(Medium),緊湊模式(Compact),大模式(Large)和巨模式(Huge)。它們之間的關系如下圖所示。用戶可以按照自己的程序大小及需要進行選擇。
│ 小程序 │ 大程序
━━━━┿━━━━━━┿━━━━━━━━
小數據 │ 微,小 │ 中
大數據 │ 緊湊 │ 大,巨
所謂小程序就是指程序只有一個程序段,大小不超過64KB,缺省的碼(函數)指針是near(近程指針)。所謂大程序就是指程序只有多個程序段,每個程序段不超過64KB,但總程序量可超過64KB,缺省的碼指針是far(遠程指針)。小數據就是指數據只有一個數據段,缺省的數據指針是near。大數據就是指數據有多個數據段,缺省的數據指針是far。
由上可知,我們所說的只有一個代碼段的程序是小程序,它的代碼不超過64kb,在編譯時會以默認的編譯模式:小模式來編譯。即前面研究裡所說的tcc a.c生成的exe文件有一個代碼段,一個棧和數據段就可以理解了。我們用TC2.0的時候不可缺少的相關文件裡有關編譯模式的是c0s.obj和cs.lib,所以TC2.0默認的編譯模式是小模式。所以默認的編譯只能編譯代碼量不超過64kb的文件。
關於不同模式的區別,查詢資料如下:
C語言編譯模式—微模式(Tiny)
在微模式下程序中的數據及代碼均放在同一段內,即它們不超過 64KB。在微模式下代碼段、堆棧段和數據段的段地址均相同,即CS=DS=SS=ES。在微模式下,數據指針都是 near,一般小程序可采用此編譯模式進行編譯。還可用 DOS 中的 EXE2BIN 轉換程序將.EXE 程序轉換成.COM 程序。代碼段、數據段和堆棧段均在同一段內,對它們進行尋址時,均以同一地址偏移的參考點,具有這種特點的段又稱為屬於同一組段(DGROUP),棧是向上生長的,即每壓棧一次,棧指針SP減2,即向地址減少的方向移動,它開始的初始值指向棧底,即0xffff(64KB)。堆是向下生長的,即向增加地址的方向改變。堆和棧地址相向生長,當兩者未相遇時,便出現了自由空間。一般程序均是這種狀態,當占用棧地址較多時,兩者可能重合並覆蓋部分堆空間。
C語言編譯模式—小模式(Small)
在小模式下,程序中的代碼放在64KB的代碼段內,數據放在64KB的數據段內。在小模式下,棧段、附加數據段和數據段均指向同一地址,它們合三為一,即DS=SS=ES,指針都是near,一般程序均采用小模式編譯。在小模式下,內存分配如下圖所示。從圖中可以看出數據段、堆棧段和附加段為同一段組,即它們的偏移地址均以同一段地址為參考點。
C語言編譯模式—中模式(Medium)
在中模式下,所有數據放在64KB的數據段內,因而數據段內使用near,代碼量可以大於64KB(允許達到1MB),因而可以在不同的代碼段內,代碼段使用(far遠程指針)。這種編譯模式適用於大代碼量、小數據量的大程序。中模式下的內存分配如下圖所示。
C語言編譯模式—緊湊模式(Compact)
在緊湊模式下,數據量超過64KB時,可放在多個數據段中,數據段內的指針是(far)。代碼量不超過64KB時,可在一個段內,因而代碼段內指針為近程的(near)。但在該模式下,靜態數據仍不能超過64KB,堆用far指針來存取。緊湊模式下的內存結構如下圖所示。
C語言編譯模式—大模式(Large)
大模式下,代碼及數據均采用far指針,且都可達到1MB。靜態數據仍跟緊湊模式一樣,不能超過64KB。大模式下的內存結構如下圖所示。
C語言編譯模式—巨模式(Huge)
巨模式下,代碼段及數據段均用far指針,代碼分布在不同的代碼段內,數據也分布在不同的數據段內,它們來自不同的源程序,大堆棧只有一個。而且靜態數據大小允許超過64KB。巨模式下的內存結構如下圖所示。
即不同模式的區別在於可編譯的代碼量和數據量不一樣。那麼在編譯的時候對於超過64kb的文件該如何選擇編譯模式呢?
無論采用哪一種編譯模式,C 源程序編譯生成的代碼和數據量都不能超過64KB,對於超過的源程序,可以視代碼或數據多少將其分解成兩個或多個程序分別編譯。大代碼量程序要選用大代碼編譯模式(中模式、大模式和巨模式),大數據量程序應選用大數據編譯模式(緊湊模式、大模式和巨模式),這樣編譯生成的.obj 文件將會帶給連接程序信息,將代碼和數據安排在不同段內。這樣生成的.exe 文件在加載時將告訴 DOS 該程序應如何裝入代碼段和數據段,如何初始化寄存器。這樣,就可確定在不同編譯模式下開辟數據區的大小,即大於64KB,或不超過64KB。
2、解決的問題
(1)TC2.0集成了tcc.exe、tlink.exe,並且包含了更多的功能。而tcc.exe、tlink.exe、c0s.obj、cs.lib、emu.lib、maths.lib是編譯c文件必不可少的文件。
(2)怎麼打印所有函數的段地址和偏移地址?
答:可以用printf(“%lx”,(long)函數名);來打印,這樣段地址和偏移地址是連在一起的,也可以用printf(“%x %x”,_CS,函數名);來打印,這樣段地址和偏移地址是分開的。
(3)為什麼輸出的段地址和debug調試的段地址不一樣?
答:debug調試要加載更多的函數,所以main函數的段地址會相對較大。
(4)用tcc hello.c生成的文件可有兩個段,一個為代碼段,一個為棧和數據段。所以子函數和主函數都要在同一個代碼段裡。那麼如果代碼量超過64kb,一個段存放不下,怎麼辦?
答:那應該使用別的內存編譯模式,TC下默認的模式是小模式,只支持64kb以下的代碼和數據,若代碼和數據超過64kb,可以使用大模式或者巨模式。
3、研討會解決的問題
(1)如果缺少相關文件,tcc.exe會調用tlink.exe嗎?生成的obj文件是含有其他相關文件嗎?
答:根據討論,如果缺少相關文件,tcc.exe調用了tlink.exe但是無法找到文件,需要再用tlink.exe來連接obj文件,生成exe文件。如果缺少相關文件,tlink會連接其他相關文件。經過實驗,如果缺少maths.lib,程序能夠輸出helloworld,而如果缺少c0s.obj或者cs.lib編譯成的exe文件就會運行出錯,這是因為maths.lib是運算相關的庫,如果程序裡沒有運算的話,即使缺少也不影響程序的執行,而c0s.obj或者cs.lib是程序啟動運行所需要的文件,所以一旦缺少就會出錯。
修改:經過再次的實驗,我發現缺少maths.lib文件,編譯連接生成的exe文件也會顯示出錯,這說明之前的結論是不成立的。tcc.exe的功能是把c源文件編譯成二進制obj文件,再調用tlink.exe進行連接生成exe文件。即只要執行一條命令就能把c源文件編譯連接成可執行的exe文件。我們沒有改變tcc.exe文件的內容,那麼tcc還是會調用tlink,只是因為相關文件不全而出錯導致tlink沒有正確執行連接而已。實驗發現先用tcc -c hello.c或者tcc -linclude hello.c將源文件編譯成obj文件,再連接成exe文件還是執行出錯,在網上有資料說這樣編譯連接會導致返回錯誤,但沒有說明具體原因。我覺得可能單獨用tlink連接hello.obj會在調用相關文件上出錯,就是說tlink沒有調用相關文件的能力,只是tcc調用它的時候告訴它應該按什麼順序來連接,它才能正常連接。但是這個猜想和上面的問題一樣,我暫時還找不到方法和資料來驗證,希望學長能在衍生課上講一講,具體的問題是這樣的:
如果缺少相關文件,生成的obj文件是含有其他相關文件嗎?為什麼一定要tcc.exe調用tlink.exe才能生成正確的可執行文件?
(2)為何在原來的平台上即使沒有tcc和tlink也能夠編譯鏈接成功。
答:TC2.0集成有多種編譯器,c語言和匯編語言是可以混合編譯的。如果出現匯編語言,那麼TC2.0就會調用tcc.exe來進行編譯。Turbo c包有兩種編譯器,集成開發環境下的叫做TC.EXE和命令行方式的叫做TCC.EXE. 集成開發環境包括:集成編輯器、命令行編譯器、連接器、調試器。
(3)庫文件是怎麼搜索的?
答:在turboc.CFG中可以指定tcc可以用來搜索的庫文件的位置。但是用TC2.0修改路徑不會保存在turboc.CFG中,而是生成另一個配置文件。
(4)三種模式,是否可以互相的替換
答:其實c語言編譯有6種模式,這6種模式編譯的結果都是一樣的,只是支持的數據大小和程序大小不一樣。
(5)為何打印出來的段地址的值和用debug調試的時候出來的地址的值是不一樣的?
答:編譯時給定了偏移地址,載入時cmd或者debug再給定段地址,所以段地址不一樣。
(6)假如代碼量超過了64K後,會如何?
答:那麼就不能用默認的內存編譯模式(小模式)來編譯,會出錯。應該用支持大程序的模式來編譯(如中模式、大模式、巨模式)。編譯器會把代碼分成幾個不超過64kb的程序來編譯。
(7)為什麼打印出來的偏移地址改變,在平常的時候main函數的偏移地址是1fa但是此時的main函數的偏移地址不是這樣的了?
答:主函數的偏移地址不一定是1fa,而是第一個程序的偏移地址是1fa。之後函數的偏移地址按程序的長度發生變化。其實原理和匯編語言裡不同段的地址不同是一樣的。C0s.Obj裡的函數加載完後正好到了1fa處應該加載源文件的內容了,這時那個函數在第一個就把哪個函數放在1fa處。
(8)如何用一條語句打印出段地址和偏移地址。
答:可以用printf(“%lx”,main);打印出段地址和偏移地址。
4、學習感想
其實TC2.0也是別人寫的方便開發者使用的一個程序。它是調用了tcc.exe、tlink.exe和一些庫文件來實現程序的編譯連接,而更高級的開發工具只是編寫的功能更全、調用的文件更多、采用更高級的編譯器和連接器而已。它們都是由這一個小小的tcc.exe衍生開的。龐大的、復雜的事物,其實可能起核心作用的就是那麼一點點東西而已。
這一個研究涉及到很多前面研究的知識,特別是匯編。所以掌握了匯編語言為我們學習C語言打下了良好的基礎。