天天看點

深入認識 Turbo C 編譯器

深入認識 Turbo C 編譯器

有誰真正的了解過一個編譯器呢?許多人認為 TC 很簡單很落後,但是即便是這樣簡單

的工具,到底有幾個人真正的深入了解了呢?一個簡單的編譯器都不能了解,如何能成為

高手,如何能深入的使用更加進階的工具呢?不要以為自己使用的是VC就很了不起,因為

使用這樣傻瓜化的工具隻能讓你看不到事物的本質。接下來我們就來深入的認識Turbo C

編譯器。

     廣義的編譯器,包括了代碼編譯器(compiler),目标檔案連結器(linker),庫檔案管理工

具(如tc的tlib,gcc的ar),編譯驅動工具(如VC的NMake,gcc的make),ANSI c/c++标準的

頭檔案和庫檔案,擴充的頭檔案和庫檔案,內建開發環境(IDE),等等與編譯相關的工具,

所有這些工具的集合,就組成了廣義上的編譯器。

     狹義的編譯器,則僅指compiler。compiler隻負責将源代碼,即.c/.cxx/.cpp檔案編譯成

為目标檔案.o/.obj。編譯過程的輸入是源檔案,包括自己書寫的.c和.h以及系統提供的.h文

件,編譯的輸出是目标檔案。需要強調的一點時,在compile階段,隻處理源檔案,是以不

需要庫檔案和額外的目标檔案的參與,是以,隻要代碼在文法上沒有錯誤,compile就一定

能産生目标檔案。

     對于一個廣義的編譯器來說以下幾個部分是必備的:1.compiler,2.linker,3.系統提供

的頭檔案和庫檔案。前面已經介紹了compiler,接下來看linker。

     linker的功能是将目标檔案進行裝配,将浮動的位址變為确定的位址,這個工作是通過修

改目标檔案的重定位項來實作的,其具體的過程可以參考 "Linker & loader"這本書,這是一

本詳細介紹linker和loader的好書,在此做個推薦。總之,link這一階段處理的輸入是目标文

件,其輸出是可執行檔案,或動态庫。

     任何一個編譯器都會提供庫檔案和與之對應的頭檔案,C/C++編譯器一般都提供ANSI

C/C++的庫和相應的頭檔案。

     從現在起我們就需要建立起一個概念,就是廣義的編譯過程,實際上是由編譯和連結兩

個基本步驟組成的,如果能深刻的了解這兩個步驟,就是一大進步了。

     在編譯器裡,有一些預設的規定,我們需要了解。在編譯器中,bin目錄用于存放

compiler、linker等工具,include目錄用于存放頭檔案,lib目錄用存放庫檔案,大多數的編

譯器的目錄就是按這個來組織的。

接下來看Turbo C為我們提供了些什麼(請到我的網站下載下傳我動手制作的改良版TC編譯器)。

bin目錄中:

     CPP.EXE C語言預處理工具,就是負責對源代碼進行預編譯處理,不要了解為c++編譯器

     TCC.EXE C語言的編譯器,可以将代碼編譯為目标檔案,并且能自動調用tlink連結生成可執

              行檔案

     TASM.exe    是一個彙編工具,可以将x86的彙編代碼編譯成為目标檔案

     TLink.exe   是一個連結器,負責對目标檔案、庫檔案等進行連結

     TLib.exe    是一個庫檔案管理工具,可以将多個目标檔案打包到一個庫檔案裡

     BGIOBJ.exe 可以将BGI檔案轉換為.obj檔案

     make.exe    符合GNU标準的make工具,可用于代碼編譯的管理(隻有在我制作的TC中提供)

     TURBOC.CFG tcc預設的編譯參數配置檔案

以上所有的工具的使用方法都可以直接鍵入相應的指令進行檢視,如鍵入tcc即可看到tcc的

使用方法,是以這裡不再講解。

BGI目錄中:

     EGAVGA.BGI 是EGAVGA的bgi驅動

FONT目錄中:存放了BGI所使用到的各種字型檔案

INCLUDE目錄中:是Turbo C的庫函數的所有的頭檔案,當要使用某個庫函數時可以在這個

目錄下搜尋,找到其所在檔案和原型,這裡不在詳細叙述。

重點講一下Lib目錄:

     init.obj檔案是C語言的啟動代碼,它負責建立C程式運作的堆棧、初始化記憶體、調用C入口函

數等。這部分代碼是使用彙編書寫的,其源代碼可以在TC(官方版)裡找到,名稱為Init.ASM。

     c0t.obj、c0s.obj、c0m.obj、c0c.obj、c0l.obj和c0h.obj檔案,都是c code的入口函數實作,

入口函數将會讀取環境變量,并調用c語言中的main函數,将指令行參數傳入main函數中,

之後的控制權就交給了main函數,也就是我們常說的C的主函數main。由于Turbo C中有不

同的記憶體模式,是以以上6個檔案分别對應TC中6種不同的記憶體模式。

     cc.lib、ch.lib、cl.lib、cm.lib、cs.lib五個檔案都是TC提供的ANSI C标準庫的庫檔案,分别對

用不同的記憶體模式:

     cc compact模式

     ch huge模式

     cl large模式

     cm medium模式

     cs small模式

由于不同模式參數的入棧方式、函數的調用方式等等都各不一樣,是以代碼也不一樣,是以

需要分别提供各個模式的庫檔案。

     再講一下Turbo C中的記憶體模式。記憶體模式的出現不是由編譯器決定的,而是由處理器的尋

址方式決定的,在8086處理器中為了在16位寄存器的基礎上尋址20位的位址,引入了段寄

存器和分段尋址的方式。在編譯器這一級,利用這種段式的尋址方式,可以實作多種不同的

存儲配置設定方法,是以就産生了所謂的不同的記憶體模式。

     1. tiny模式:    程式和資料在一個64K位元組的段内

     2. small模式:   獨立的代碼段(64KB)和獨立的資料段(64KB)

     3. medium模式: 單個資料段(64KB)和多個代碼段(1MB)

     4. compack模式:單個代碼段(64KB)和多個資料段(1MB)

     5. large模式:   多個代碼段(1MB)和多個資料段(1MB),資料指針不能跨越段邊界,否則将回繞

     6. huge模式:    多個代碼段(1MB)和多個資料段(1MB),資料指針可以跨越段邊界,不會回繞

     在TC中記憶體模式與far、near、huge等關鍵字又有着密切的關系。在tiny、small模式下,所

有的函數定義、全局變量定義和指針變量的定義,如果沒有顯示的加上far、near、huge等

關鍵字,都預設為使用了near關鍵字;在medium模式下,函數定義預設使用了far關鍵字,

變量定義預設使用了near關鍵字;在compact模式下函數定義模式使用了near關鍵字,變量

定義預設使用了far關鍵字;large模式下函數定義和變量定義模認使用了far關鍵字;huge模

式下函數定義模認使用了far關鍵字,變量定義預設使用了huge關鍵字。

     near、far、huge關鍵字的真正含義是什麼?這三個關鍵字隻能用于修改函數、全局變量和

指針變量,對于非指針類型的局部變量,這些關鍵字沒有實際意義。這些關鍵字用于修飾函

數時,huge的含義與far相同,用于指明該函數的調用方式為far調用方式,即調用時需要一

個段值和一個段偏移組成的32bits調用位址,使用far call進行跳轉,跳轉前先壓棧儲存目前

CS:IP。near修飾函數時,用于指明該函數的調用方式為near調用方式,調用時隻需要一個

16bits的近位址,即目前CS的段内偏移。

     當這三個關鍵字用于修飾指針時,near型指針實質上為16bits的無符号整型數,該整數給出

了所指向變量在目前資料段内的偏移位址,也就是說,在使用near型指針尋址時實際上是進

行如下的尋址操作:[DS:指針變量值]。對于far型的指針變量,可以尋址1MB位址空間的任

意一個地方,far型指針的實質是一個32bits的整型數,高16bits為段值,低16bits為段内偏

移,Turbo C中在使用far型指針時,會先将高16bits放入ES寄存器中,然後再進行如下的尋

址操作:[ES:指針變量低16bits值]。對于hug型的指針變量,與far性指針變量的不同之處在

于,在對far型指針變量進行+/++/-/--等操作時,far型指針變量保持段值不變(也就是高

16bits),而隻對段内偏移進行加減操作,是以會出現段内回繞的現象,而huge型的指針,

在進行加減操作時将會自動的改變段值,不會出現段内回繞。是以給人的感覺就是huge指針

能比far指針尋址更大的記憶體空間。

     對于局部變量,由于是建立在堆棧上,是以near、far等關鍵字将不具備任何意義,因為創

建在堆棧上的變量的尋址方式就隻有一種,即使用sp和bp維護函數堆棧,利用bp+/-一個

偏移來尋址函數參數變量和局部變量。這樣的尋址方式是固定而唯一的,near和far等關鍵

字都派不上用場,這裡的near和far将沒有任何的實際含義。

     對于使用near、far和huge修飾的全局變量的含義也很容易了解了。near型的全局變量,被

配置設定到了目前的資料段上,尋址這個變量隻需要一個 16bits的偏移量,而far型全局變量在

尋址時,需要給出段值和偏移量。huge型數組可以使用大于64K的記憶體空間。

     far、near、huge型指針變量之間的互相轉換,從小尺寸的指針到大尺寸的指針将進行自動

的類型轉換,轉換方式為加上目前的DS形成32bits的指針。從大尺寸的指針到小尺寸的指針

需要進行強制類型轉換,轉換的結果為隻保留低16bits,但是這樣俄轉換沒有實際的意義或

者說用處不大,并且極其容易引入記憶體通路的錯誤,是以要嚴格避免使用。

     需要注意的是,near、far、huge三個關鍵字的使用,還需要記憶體模式的緊密配合。但并不

是說tiny模式下就不能使用near、far、huge三個關鍵字。tiny模式下同樣可以定義如下的

指針:

     char far *pbuf = 0xA0000000;

     并且我能保證這個指針能夠絕對正确的工作,對函數、全局變量的修飾也是如此。但是如何

正确的工作,如何才是最和合理的方式,請自己思考了。基本的原理我也講的很清楚,就不

再多費唇舌。

     Turbo C中,我想最為困惑的就是記憶體模式了,我也是費了很多時間和精力,通過分析Turbo C

的彙編代碼的出的以上結論。許多朋友都對此很困惑,是以這部分重點講了下,和大家分享。

如有不正确之處,請不吝賜教,旨在抛磚引玉。tcc編譯彙編代碼的方法為:

tcc -c -mt -S filename.c,

-c 指明compile only,-mx用于指定記憶體模式,-S指明生成彙編代碼,如果大家有興趣可以

嘗試使用這個方法分析tcc編譯結果的彙編代碼,進而更加深刻的了解C與彙編的關系。

     當我們在編寫、制作并向使用者提供自己的庫檔案時,也需要注意記憶體模式的比對,否則在進

行連結時會存在問題。一個較為簡單的方法就是向使用者提供全套記憶體模式的庫檔案,這也是

Turbo C的ANSI C庫的做法,前文已經提到。如果不想提供多個記憶體模式的庫檔案,可以對

程式中每個函數、全局變量和指針變量進行顯式的類型聲明,以精确定義每個變量的類型。

     關于TC中各種編譯工具的使用方法,可以直接參考其幫助,并且許多參考文檔都有說明,這

裡我就不再詳細介紹了。關于GNU的make工具的使用,同樣也可以在網上找到參考資料,

是以不再介紹。還有就是關于Turboc的BGI驅動的,我也研究過多時,我這裡有詳細的參考

文檔,并且已經實作了一個BGI的架構,對于有興趣自己開發BGI的朋友,我們可以交流。當

然Turbo C最大的魅力,也是最讓我着迷的也就是它簡單而直接的圖形程式設計,可以直接的訪

問硬體資源,是以能收獲許多底層、硬體相關的知識。當然Turbo C的圖形程式設計是一個很大

的課題,我也在不斷的學習和研究之中,如果有機會也會繼續寫作相關的文章。

     關于TC,還有許多值得介紹的,但是一時也想不起來了,本來打算寫的更加細緻一點,但是

心中隻有這麼點墨水,現在墨水已經寫幹了,等以後有時間,有墨水以後再繼續這個話題吧。

OK,結束了。

引自: http://bbs.znpc.net/viewthread.php?tid=2858&extra=page%3D1

繼續閱讀