實現(xiàn)簡易的C語言編譯器(part 0)

0.1 引言

? ? ? ? 工作之余,閑來無事祭刚,便根據(jù)多方搜集的資料,基于Python實現(xiàn)了一個簡易的C語言編譯器好啰,可以稱之為SCC(Simplified C Compiler)。整理了這段時間的學(xué)習(xí)過程儿奶,也分享出來框往,讓更多愿意了解編譯器的人少走一些彎路,提供更多可以參考的資料闯捎。

? ? ? ? 相信如果開始學(xué)習(xí)這部分知識椰弊,可能大都是從《??書》這類經(jīng)典書籍開始的。但是相信很多人屏住呼吸翻開一頁又一頁瓤鼻,又學(xué)到了多少知識秉版,就因人而異了。反正茬祷,我沒有看完那本書清焕,反倒是這一系列文章(Let's Build A Simple Interpreter)淺顯易懂地解釋了Pascal語言解釋器的實現(xiàn)方法,對我非常有啟發(fā)和幫助祭犯。編譯器并不是深不可測秸妥,只是從小坑里面好爬出來一些罷了。

? ? ? ? 在進入下一個部分之前盹憎,讓我們先想一想筛峭,為什么要學(xué)習(xí)編譯器知識铐刘。

  • 沒事干陪每,像我一樣,可以找點虐心的事情做镰吵。
  • 以后寫程序遇到bug檩禾,就可以拓寬debug的范圍了。
  • 成為寫出(C++)++的那個人疤祭。
  • ......

? ? ? ? 一切都得有一個目標(biāo)盼产,不然就沒辦法堅持下去。對于我自己而言勺馆,學(xué)習(xí)底層的知識戏售,讓自己能夠系統(tǒng)性地思考,去面對各種上層調(diào)用帶來問題草穆,非常具有挑戰(zhàn)性灌灾。

? ? ? ? 好了,閑話少許悲柱,下面進入正題锋喜。

0.2 初識編譯器

? ? ? ? 這里簡單介紹一下編譯器的組成:


圖1. 編譯器組成

0.2.1 前處理

? ? ? ? 這部分主要做三件事情:

  • 處理頭文件
    #include "stdio.h"
    按照頭文件引用順序嵌套地將頭文件的內(nèi)容展開到當(dāng)前文件中。如果嵌套引用到的文件很多,最終參與編譯的源文件內(nèi)容肯定超過了文件中原本的那些代碼嘿般。只是大部分時候段标,我們將聲明(.h文件)與實現(xiàn)(.c文件)分離,而.c文件可以單獨生成目標(biāo)文件(后綴名為.o)炉奴,只需要在鏈接的時候添加上即可逼庞。因此并不需要全部展開到當(dāng)前文件中。
  • 處理預(yù)編譯指令
    C語言有很多的預(yù)編譯指令瞻赶。比如往堡,非常常用的:
    #if XXX
    ...
    #elif XXX
    ...
    #else
    ...
    #endif
    
    實際上,現(xiàn)在的IDE工具已經(jīng)能夠直接進行辨識共耍,直接就能告訴你用哪一塊代碼虑灰,剩下的就直接忽略了,不會進入編譯過程痹兜。
  • 展開宏定義
    #define add(x, y) ((x) + (y))
    ((x) + (y))將代碼中的add(x, y)全部替換穆咐,這也是為什么在學(xué)習(xí)C語言的過程中,不要吝惜用括號的緣故字旭;同時对湃,宏定義末尾也不能加分號等等。因此遗淳,當(dāng)明白編譯器怎么處理宏定義的時候拍柒,那么使用宏定義就能游刃有余了。

0.2.2 編譯

? ? ? ? 經(jīng)過前處理過程處理的代碼就開始進入編譯過程屈暗〔鹧叮回顧一下,我們遇到的編譯錯誤主要有哪些养叛?以下面這段代碼為例:

struct Point
{
    int x;
    int y;
}  // <- missing ';'  (2

struct Point pt = {1, 2};
int main()
{
    if (pt.x <> 2) // <- '<>' no such operator (1
        b = 2;  // <- 'b' is undefined (3
    return 0;  
}

? ? ? ? 我在這里列舉了三類錯誤种呐,已經(jīng)分別標(biāo)注在上面對應(yīng)的代碼后面。那么弃甥,再設(shè)想一下爽室,我們應(yīng)該如何編寫代碼將這些錯誤找出來呢?
? ? ? ? 很明顯淆攻,第一種錯誤阔墩,也就是<>這種符號性質(zhì)的錯誤,只需要從頭到尾遍歷一遍瓶珊,就可以發(fā)現(xiàn)啸箫,根本不用做額外的工作。這就是我們將要介紹的詞法分析艰毒。
? ? ? ? 對于第二種錯誤筐高,如果不是結(jié)構(gòu)體,而只是一般的函數(shù)塊,也是不需要分號的柑土。這時蜀肘,我們必須要能夠知道這里應(yīng)該出現(xiàn)什么符號,不應(yīng)該出現(xiàn)什么符號稽屏。這就需要對代碼的結(jié)構(gòu)有一定的認知扮宠,也就是語法分析
? ? ? ? 那前面分析手段辦不到的狐榔,自然就留給語義分析去做了:進行變量的聲明檢查坛增。

0.2.2.1 詞法分析

? ? ? ? 詞法分析是一個化整為零的過程。它從頭到尾將源代碼拆分成一個個的單元薄腻,稱之為token收捣。這些token按照空格、換行符和引號等進行拆分庵楷,可以是變量名罢艾、關(guān)鍵字、運算符號和其它字符尽纽。由于C語言并沒有定義<>這樣的二元比較操作符咐蚯,此處就會產(chǎn)生錯誤提示信息。

0.2.2.2 語法分析

? ? ? ? 語法分析則是一個化零為整的相反過程弄贿。它將token按照定義的語法要求組成表達式春锋,語句和程序段。由于C語言要求結(jié)構(gòu)體定義必須以;結(jié)尾差凹,此處就會產(chǎn)生語法錯誤期奔。這是很多人開始學(xué)C語言容易忘記的地方。
? ? ? ? 一些時候直奋,我們可能會遇到IDE提示一大堆錯誤能庆,然后去出錯的地方看,覺得也沒有錯誤脚线。其實這個時候,就是在最開始出錯的地方前面弥搞,缺少;所致邮绿。不過,現(xiàn)在編譯器功能越來越強大攀例,很多時候能夠直接準(zhǔn)確定位錯誤船逮。

0.2.2.3 語義分析

? ? ? ? 詞法分析只是將token組成了符合語法邏輯結(jié)構(gòu)的片段,還需要語義分析進行上下文檢查粤铭,即判斷變量挖胃、函數(shù)是否已經(jīng)定義或者類型是否匹配。顯然,變量b開始使用的時候并沒有定義酱鸭,此處便是第三種語法錯誤吗垮。

0.2.2.4 匯編語言生成

? ? ? ? 當(dāng)然,經(jīng)過了上面三個過程的仔細檢查凹髓,我們可以放心地為源代碼生成匯編語言代碼了烁登。目前,主流的匯編語言格式有Intel和AT&T兩種蔚舀,雖然格式還是有一定的差別饵沧,但是萬變不離其中,本質(zhì)上是相通的赌躺。
? ? ? ? 這一步狼牺,也是最終影響程序運行性能的關(guān)鍵。我們將在后面詳細討論礼患。

0.2.3 匯編

? ? ? ? 匯編語言代碼還需要經(jīng)過匯編過程生成二進制代碼锁右,每條匯編指令都會生成一個相對于某個基地址的偏移地址⊙忍基地址大多數(shù)情況下都不是實際的物理地址咏瑟。因此,并不能直接運行痪署。

0.2.4 鏈接

? ? ? ? 直到通過鏈接器對多個二進制代碼的地址偏移重新編排码泞,得到具有正確物理地址的二進制代碼,這個時候狼犯,才能直接運行余寥。

0.3 編譯器命令行

? ? ? ? 考慮hello.c文件下的代碼:

#include "stdio.h"

int main(int argc, char* argv[])
{
    printf("hello world!");
    return 0;
}

接下來我們將使用成熟的C語言編譯器對每一個過程進行命令行操作,從而與后面我們實際編寫的代碼生成的結(jié)果相比較悯森。

  • 前處理過程
    clang -E hello.c -o hello.e
  • 語法分析和語義分析
    clang -fsyntax-only hello.c
  • 匯編語言生成
    clang -S hello.c -o hello.s
  • 匯編
    clang -o hello.o hello.s
  • 鏈接
    clang -o hello hello.o

更多的內(nèi)容可以詳見LLVM的官方文檔宋舷。

? ? ? ? 這樣一看,編譯器其實承擔(dān)了非常繁雜的工作瓢姻。在接下來的部分祝蝠,這些內(nèi)容都會一一呈現(xiàn)。

實現(xiàn)簡易的C語言編譯器(part 1)
實現(xiàn)簡易的C語言編譯器(part 2)
實現(xiàn)簡易的C語言編譯器(part 3)
實現(xiàn)簡易的C語言編譯器(part 4)
實現(xiàn)簡易的C語言編譯器(part 5)
實現(xiàn)簡易的C語言編譯器(part 6)
實現(xiàn)簡易的C語言編譯器(part 7)
實現(xiàn)簡易的C語言編譯器(part 8)
實現(xiàn)簡易的C語言編譯器(part 9)
實現(xiàn)簡易的C語言編譯器(part 10)
實現(xiàn)簡易的C語言編譯器(part 11)
實現(xiàn)簡易的C語言編譯器(part 12)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幻碱,一起剝皮案震驚了整個濱河市绎狭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌褥傍,老刑警劉巖儡嘶,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恍风,居然都是意外死亡蹦狂,警方通過查閱死者的電腦和手機誓篱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凯楔,“玉大人窜骄,你說我怎么就攤上這事√淅保” “怎么了啊研?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鸥拧。 經(jīng)常有香客問我党远,道長,這世上最難降的妖魔是什么富弦? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任沟娱,我火速辦了婚禮,結(jié)果婚禮上腕柜,老公的妹妹穿的比我還像新娘济似。我一直安慰自己,他們只是感情好盏缤,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布砰蠢。 她就那樣靜靜地躺著,像睡著了一般唉铜。 火紅的嫁衣襯著肌膚如雪台舱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天潭流,我揣著相機與錄音竞惋,去河邊找鬼。 笑死灰嫉,一個胖子當(dāng)著我的面吹牛拆宛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讼撒,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼浑厚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了椿肩?” 一聲冷哼從身側(cè)響起瞻颂,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎郑象,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茬末,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡厂榛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年盖矫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片击奶。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡辈双,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柜砾,到底是詐尸還是另有隱情湃望,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布痰驱,位于F島的核電站证芭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏担映。R本人自食惡果不足惜废士,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝇完。 院中可真熱鬧官硝,春花似錦、人聲如沸短蜕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朋魔。三九已至岖研,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铺厨,已是汗流浹背缎玫。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留解滓,地道東北人赃磨。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像洼裤,于是被迫代替她去往敵國和親邻辉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容