編程達(dá)到一個(gè)高的境界就是自制腳本語(yǔ)言,通過(guò)這可以精通編程里面的高深的技術(shù),如編譯原理、語(yǔ)言處理器、編譯器與解釋器,這些都是代表一個(gè)程序員實(shí)力的技術(shù)。 每個(gè)程序員都有實(shí)現(xiàn)屬于自己編程語(yǔ)言的夢(mèng)想,說(shuō)其是夢(mèng)想,原因是實(shí)現(xiàn)的難度很大......這種情況一直持續(xù)到《自制編程語(yǔ)言》的出現(xiàn)。 《自制編程語(yǔ)言》 鄭鋼 著 本書(shū)講的是純粹的技術(shù)“干貨”,符合鄭鋼老師一貫的寫作風(fēng)格,這是他靜心寫出來(lái)的東西,內(nèi)容滿滿,很值得閱讀。滴滴系統(tǒng)部技術(shù)高級(jí)總監(jiān)于曉聲說(shuō):“很高興能成為本書(shū)的首批讀者,也很高興能為本書(shū)寫推薦序。” 剛拿到本書(shū)手稿時(shí),從書(shū)名上我意識(shí)到這是對(duì)我胃口的書(shū)。果然,整書(shū)閱讀以后,收獲頗多。如今程序員的開(kāi)發(fā)成本已經(jīng)很低了,項(xiàng)目中有各種成熟的框架和庫(kù)可供選擇和使用,但還有人能靜下心來(lái)研究編譯器這么底層的技術(shù),實(shí)屬難得。本書(shū)猶如一把火炬,點(diǎn)燃了技術(shù)人內(nèi)心對(duì)開(kāi)發(fā)的熱情。 依稀記得2010年年初在百度與鄭鋼初次見(jiàn)面的情景,那時(shí)他工作之余的時(shí)間基本都用在向各個(gè)技術(shù)專家請(qǐng)教、討論各類技術(shù)問(wèn)題上,他是我?guī)н^(guò)的人中最勤奮的人之一。時(shí)間荏苒,一分耕耘一分收獲,看到他今天的成長(zhǎng),尤感欣慰。 本書(shū)講述了一門腳本語(yǔ)言(sparrow)的開(kāi)發(fā)過(guò)程,這是一本“步步為營(yíng)”式的書(shū)籍,延續(xù)了他編寫《操作系統(tǒng)真象還原》的風(fēng)格,手把手地教讀者從零實(shí)現(xiàn)一門語(yǔ)言,從原理到實(shí)踐每一步都有實(shí)際的代碼和詳盡的原理說(shuō)明,通過(guò)運(yùn)行書(shū)中各小節(jié)中的代碼,讀者可以很輕松地掌握各個(gè)細(xì)節(jié),因此本書(shū)的學(xué)習(xí)曲線并不陡峭,甚至很平坦。 另外,值得欣喜的是,本書(shū)所編寫的腳本語(yǔ)言并不是用Java、C++等入門難度略大的語(yǔ)言實(shí)現(xiàn)的,而是用C語(yǔ)言,這是我們學(xué)習(xí)編程的基礎(chǔ)語(yǔ)言。也就是說(shuō),本書(shū)并不需要專業(yè)的開(kāi)發(fā)經(jīng)驗(yàn)即可上手學(xué)習(xí)。另外,在實(shí)現(xiàn)過(guò)程中并未用到復(fù)雜的庫(kù)函數(shù)或系統(tǒng)調(diào)用,可以負(fù)責(zé)地說(shuō),本書(shū)已經(jīng)將學(xué)習(xí)成本降到最低。 C語(yǔ)言是一種面向過(guò)程的語(yǔ)言,如何用一種面向過(guò)程的語(yǔ)言去實(shí)現(xiàn)一種面向?qū)ο蟮恼Z(yǔ)言很有意思。另外,PHP和Perl語(yǔ)言雖然也實(shí)現(xiàn)了類,但它們其實(shí)是一種面向過(guò)程的語(yǔ)言,并不是純粹的面向?qū)ο笳Z(yǔ)言,而sparrow語(yǔ)言是一種純粹的面向?qū)ο笳Z(yǔ)言,它在設(shè)計(jì)之初就采用對(duì)象的方式來(lái)處理腳本語(yǔ)言中類的成員和方法,這仿佛讓我們看到了面向?qū)ο缶幊陶Z(yǔ)言的基因。 眾所周知,當(dāng)今最流行的腳本語(yǔ)言應(yīng)屬Python,Python也是用C語(yǔ)言實(shí)現(xiàn)的,也許你很好奇Python的內(nèi)部原理,但是想到它有將近 4 萬(wàn)行的源代碼時(shí),也許甚至不想看它的源程序了。那么研讀本書(shū)中的sparrow語(yǔ)言會(huì)是一種更好的選擇,其源碼不足7100行,閱讀過(guò)程輕松愉快,但可以學(xué)到Python這種語(yǔ)言的實(shí)現(xiàn)原理。 對(duì)于腳本語(yǔ)言來(lái)說(shuō),兩個(gè)重要方面就是垃圾回收和運(yùn)行環(huán)境。垃圾回收就是我們平時(shí)所說(shuō)的GC(Garbage Collection)。有了GC,程序員不需要手工釋放所分配的對(duì)象,可以使精力專注于業(yè)務(wù)邏輯而不用擔(dān)心內(nèi)存泄漏問(wèn)題。 在sparrow語(yǔ)言中同樣實(shí)現(xiàn)了GC,通過(guò)此部分代碼你可以看到GC 的原理,以及哪些對(duì)象才能被回收。 運(yùn)行時(shí)環(huán)境就是腳本語(yǔ)言中的虛擬機(jī),即VM(如Java語(yǔ)言的JVM也是一種VM)。 腳本語(yǔ)言是通過(guò)虛擬機(jī)才能運(yùn)行的,如何把編譯器生成的操作碼轉(zhuǎn)換為實(shí)際的代碼行為,這里面的工作對(duì)大多數(shù)人來(lái)說(shuō)很神秘。相信各位在源碼中一探究竟之后會(huì)發(fā)現(xiàn):GC和VM這兩個(gè)神秘的黑盒子不過(guò)如此。 另外,也許程序員最感興趣的就是線程,關(guān)于線程在用戶態(tài)下是如何實(shí)現(xiàn)的、線程如何實(shí)現(xiàn)調(diào)度,本書(shū)將告訴你答案??傊?,但凡涉獵,開(kāi)卷有益。 為什么創(chuàng)作這本書(shū)? 很多讀者看了我寫的《操作系統(tǒng)真象還原》(一本一步步編寫操作系統(tǒng)的書(shū))書(shū)后,紛紛來(lái)信,要求我再寫一本自制編程語(yǔ)言的書(shū)。這也在情理之中,對(duì)于很多計(jì)算機(jī)從業(yè)者來(lái)說(shuō),操作系統(tǒng)和編譯器幾乎是兩座無(wú)法逾越的大山,其難度之大,令很多人員望而生畏。最終,在讀者的鼓勵(lì)下,一沖動(dòng)就答應(yīng)了寫作本書(shū),其實(shí)我很“后悔”做出這樣的決定。 為什么后悔呢?因?yàn)閷憰?shū)代價(jià)很大。 首先,寫書(shū)相當(dāng)累,占用很多精力。其次,占用自己學(xué)習(xí)的時(shí)間,在當(dāng)今個(gè)人進(jìn)步緩慢就算退步的時(shí)代,自己沒(méi)有提升技術(shù)會(huì)很恐慌。 再次,精力全放在寫書(shū)上會(huì)影響家庭、影響工作。 最后,還要負(fù)責(zé)解答許多問(wèn)題,確實(shí)很累。而且,這一次可是在創(chuàng)造編程語(yǔ)言,難度系數(shù)太高了,不亞于開(kāi)發(fā)一個(gè)操作系統(tǒng),甚至我父母都勸我:小剛,你都多大了還寫書(shū),好好過(guò)日子、踏實(shí)上班就行了。但是,我最后還是決定寫本書(shū)。 下面是我跨過(guò)重重?cái)r阻創(chuàng)作本書(shū)的動(dòng)機(jī)。 1 有夢(mèng)想,有遠(yuǎn)方 既然寫書(shū)代價(jià)那么大,那我為什么還要“明知山有虎,偏向虎山行”呢?因?yàn)槲揖褪潜贾袄匣ⅰ比サ模瑳](méi)有老虎的山就沒(méi)有探險(xiǎn)的樂(lè)趣。 2 有難度才有價(jià)值 每次遇到一件很難的工作時(shí),我先是“痛苦”,然后隨之而來(lái)的就是“興奮”,因?yàn)檫@意味著我要進(jìn)步。也許讀者會(huì)說(shuō),一定會(huì)進(jìn)步嗎?也許99%會(huì)失敗。 同樣一件事,每個(gè)人對(duì)它的態(tài)度都不同,懦夫看到的是:99%會(huì)失敗,別干了。勇士看到的是:還有1%成功的機(jī)會(huì),干吧! 只要不放棄(注意,不是堅(jiān)持),一定會(huì)成功,成功只是時(shí)間長(zhǎng)短的問(wèn)題。 3 人生的意義 人生最大的遺憾是“壯志未酬”。如果你是天才,請(qǐng)將自己的才華“揮霍”得一滴不剩,直到觸碰到自己智力上的天花板,這樣才甘心。如果你是大力士,請(qǐng)努力在奧運(yùn)賽場(chǎng)上為國(guó)爭(zhēng)光,直到累得站不起來(lái),這樣才甘心。 這正是我寫本書(shū)的信仰。 學(xué)習(xí)很累并且無(wú)止境,但是多知道一些就會(huì)有多一些的欣喜。本著“把自己的知識(shí)多掏點(diǎn)給大家”的誠(chéng)意,本書(shū)依然從第0章開(kāi)始,相對(duì)《操作系統(tǒng)真象還原》來(lái)說(shuō),本書(shū)的語(yǔ)言不再那么活潑(啰唆)了,畢竟編譯器的開(kāi)發(fā)難度略小于開(kāi)發(fā)操作系統(tǒng),沒(méi)必要穿插一些“過(guò)渡”的話題。 本書(shū)一步步地實(shí)現(xiàn)了一種稱為sparrow的編程語(yǔ)言,它是用虛擬機(jī)運(yùn)行的,因此最后還要實(shí)現(xiàn)一個(gè)虛擬機(jī)。sparrow語(yǔ)言是用C語(yǔ)言編寫的,學(xué)習(xí)的難度較低,實(shí)現(xiàn)的代碼不長(zhǎng),希望大家在學(xué)習(xí)的旅途中愉快。 為什么讀這本書(shū)? 本書(shū)是一本專門介紹自制編程語(yǔ)言的圖書(shū),書(shū)中深入淺出地講述了如何開(kāi)發(fā)一門編程語(yǔ)言,以及運(yùn)行這門編程語(yǔ)言的虛擬機(jī)。 本書(shū)主要內(nèi)容包括:腳本語(yǔ)言的功能、詞法分析器、類、對(duì)象、原生方法、自上而下算符優(yōu)先、語(yǔ)法分析、語(yǔ)義分析、虛擬機(jī)、內(nèi)建類、垃圾回收、命令行及調(diào)試等技術(shù)。 本書(shū)適合程序員閱讀,也適合對(duì)編程語(yǔ)言原理感興趣的計(jì)算機(jī)從業(yè)人員學(xué)習(xí)。 成功的基石不是堅(jiān)持,而是“不放棄” 人們常說(shuō),堅(jiān)持是成功的“前提”。我說(shuō),既然只是前提,這說(shuō)明堅(jiān)持也未必會(huì)成功。要想成功,人們需要的是成功的“基石”,而不是“前提”,這個(gè)基石就是3個(gè)字:不放棄。 大部分讀者都覺(jué)得開(kāi)發(fā)一門編程語(yǔ)言是很難的事,甚至想都不敢想,我擔(dān)心你也有這個(gè)想法,所以特意用這種方式先和你說(shuō)說(shuō)心里話:這本書(shū)你買都買了,多少發(fā)揮點(diǎn)價(jià)值才對(duì)得起買書(shū)的錢,誰(shuí)的錢也不是白來(lái)的。 首先,我并不會(huì)為了鼓勵(lì)大家而大言不慚地說(shuō)開(kāi)發(fā)語(yǔ)言“其實(shí)不難”“很容易”之類的話,相反,這個(gè)方向確實(shí)很難,而且就應(yīng)該很難,我想這也正是吸引你的地方,沒(méi)有難度哪來(lái)的價(jià)值,“其實(shí)不難、很容易”之類的話是對(duì)大家上進(jìn)心的不尊重。 其次,只有在“我也認(rèn)為很難”的前提下才能保證大部分的朋友能看懂本書(shū)。你看,在普通人眼里從A到D,需要有B和C的推理過(guò)程,一個(gè)步驟都不能少,在天才眼里,A到D是理所應(yīng)當(dāng)?shù)氖?,不需要解釋得太清楚,天才認(rèn)為B和C都是廢話,明擺著的事不需要解釋。而我不是天才,所以我會(huì)把B和C解釋清楚。 回到開(kāi)頭的話,為什么說(shuō)成功的基石不是“堅(jiān)持”而是“不放棄”呢?這兩個(gè)詞有啥區(qū)別?也許有讀者說(shuō),不放棄就是做著喜歡的事,讓自己愛(ài)上學(xué)習(xí)技術(shù)。個(gè)人覺(jué)得這有點(diǎn)不對(duì)了,我覺(jué)得我更喜歡吃喝玩樂(lè),因?yàn)槟鞘巧锏谋灸?,選擇技術(shù)的原因只是我沒(méi)那么討厭它,它是我從眾多討厭的事物中選擇的最不討厭的東西。 放棄是為了減少痛苦,堅(jiān)持是帶著痛苦繼續(xù)前行。“堅(jiān)持”是個(gè)痛苦的詞,但凡靠堅(jiān)持來(lái)做的事情必然建立在痛苦之上,而痛苦就會(huì)使人產(chǎn)生放棄的念頭,這是生物的本能。用“堅(jiān)持”來(lái)“鼓勵(lì)”自己硬著頭皮干,其實(shí)已經(jīng)輸了一半,自己認(rèn)為痛苦的事很難干下去,干不下去的原因是遇到困難時(shí)頭腦里有“放棄”的念頭,如果把這個(gè)念頭去掉,那么,只要活著,成功無(wú)非是時(shí)間長(zhǎng)短的問(wèn)題。這個(gè)念頭其實(shí)就是心理預(yù)期,“提前”做好心理預(yù)期很重要。 總之,不要給自己“可以放棄”的念頭,不要讓“可以放棄”成為一種選項(xiàng),把這個(gè)選項(xiàng)去掉,那么,只剩下成功。 你懂編程語(yǔ)言的“心”嗎 先來(lái)猜猜這是什么? 它是一種人人必不可少,擁有多種顏色、多種外形的物品。 它是一種質(zhì)地柔軟,可使人免受風(fēng)寒,給予人們溫暖的日常物品。 它是一種使人更加美麗,更受年輕女性歡迎的物品。 它是一種用紐扣、拉鏈或繩帶綁定到身體上的物品。 猜到了嗎?其實(shí)這是對(duì)“衣服”的描述。由于我們都知道什么是衣服,因此我們認(rèn)為以上4種描述都是正確的,通過(guò)“免受風(fēng)寒”這4個(gè)字便有可能想到是衣服。但對(duì)于沒(méi)見(jiàn)過(guò)衣服的人,比如剛出生的小孩兒,他肯定還是不懂,甚至不知道什么是紐扣。 什么是編程語(yǔ)言呢?以下摘自百度百科。 (1)“編程語(yǔ)言'(programming language),是用來(lái)定義計(jì)算機(jī)程序的形式語(yǔ)言。它是一種被標(biāo)準(zhǔn)化的交流技巧,用來(lái)向計(jì)算機(jī)發(fā)出指令…… (2)編程語(yǔ)言的描述一般可以分為語(yǔ)法及語(yǔ)義。語(yǔ)法是說(shuō)明編程語(yǔ)言中,哪些符號(hào)或文字的組合方式是正確的,語(yǔ)義則是對(duì)于編程的解釋…… (3)編程語(yǔ)言俗稱“計(jì)算機(jī)語(yǔ)言”,種類非常多,總的來(lái)說(shuō)可以分成機(jī)器語(yǔ)言、匯編語(yǔ)言、高級(jí)語(yǔ)言三大類。程序是計(jì)算機(jī)要執(zhí)行的指令的集合,而程序全部都是用我們所掌握的語(yǔ)言來(lái)編寫的…… 就像剛才我對(duì)衣服的描述,以上的3個(gè)概念,懂的人早已經(jīng)懂了,不懂的人還是不懂,回答顯得很“雞肋”。因?yàn)閷?duì)于編程語(yǔ)言的理解并不在語(yǔ)言本身,而是在編譯器,編譯器是編程語(yǔ)言的“心”,而我們很少有人像了解衣服那樣了解編譯器,因此對(duì)于我們大多數(shù)人來(lái)說(shuō)只是熟悉了語(yǔ)言的語(yǔ)法,僅僅是“會(huì)用”而已。 那什么是編程語(yǔ)言呢?無(wú)論我用多少文字都不足以表述精準(zhǔn)與全面,因?yàn)檎Z(yǔ)言的本質(zhì)就是編譯器,等你了解編譯器后,答案自在心中。目前我只能給出同樣“雞肋”的答案—編程語(yǔ)言是編譯器用來(lái)“將人類思想轉(zhuǎn)換為計(jì)算機(jī)行為”的語(yǔ)法規(guī)則。 編程語(yǔ)言的來(lái)歷 世界上本沒(méi)有編程語(yǔ)言,有的只是編譯器。語(yǔ)言本身只是一系列的語(yǔ)法規(guī)則, 這個(gè)規(guī)則對(duì)應(yīng)的“行為”才是我們編程的“意圖”,因此從“規(guī)則”到“行為”解析便是語(yǔ)言的本質(zhì),這就是編譯器所做的工作。 估計(jì)大伙兒都知道,如果想輸出字符串,在PHP語(yǔ)言中可以用語(yǔ)句echo,在C語(yǔ)言中使用printf函數(shù),在C++中使用cout,這說(shuō)明不同的規(guī)則對(duì)應(yīng)相同的行為,因此語(yǔ)言規(guī)則的多樣性只是迷惑人的外表,而本質(zhì)的行為都是一樣的,萬(wàn)變不離其宗。 并不是“打印”功能就一定得是print、out等相關(guān)的字眼兒,那是編譯器的設(shè)計(jì)者為了用戶使用方便(當(dāng)然也是為了他自己設(shè)計(jì)方便)而采用了大伙兒有共識(shí)的關(guān)鍵字,避免不必要的混亂。 語(yǔ)言一定要用更底層的語(yǔ)言來(lái)編寫嗎 有這個(gè)疑問(wèn)并不奇怪,比如: (1)Python是用C寫的,C較Python來(lái)說(shuō)更適合底層執(zhí)行。 (2)C代碼在編譯后會(huì)轉(zhuǎn)換為更底層的匯編代碼給匯編器,再由匯編器將匯編代碼轉(zhuǎn)換為機(jī)器碼。 因此給人的感覺(jué)是,一種語(yǔ)言必須要用更底層的語(yǔ)言來(lái)實(shí)現(xiàn),其實(shí)這是個(gè)誤解。C只是起初是用匯編語(yǔ)言寫的,因?yàn)樵贑語(yǔ)言之前只有匯編語(yǔ)言和機(jī)器語(yǔ)言。人總是懶惰的,肯定是挑最方便的用,匯編語(yǔ)言好歹是機(jī)器語(yǔ)言的符號(hào)化,因此相對(duì)來(lái)說(shuō)更好用一些,所以只好用匯編來(lái)編寫C語(yǔ)言,等第一版C語(yǔ)言誕生后,他們就用C語(yǔ)言來(lái)寫了。 什么?用C來(lái)編寫C?有些讀者內(nèi)心就崩潰了,似乎像是陷入了死循環(huán)。其實(shí)這根本不是一回事,因?yàn)槠鹱饔玫牟⒉皇荂語(yǔ)言,而是C編譯器。語(yǔ)言只是規(guī)則,編譯器產(chǎn)生的行為才是最關(guān)鍵的,編譯器就是個(gè)程序,C代碼只是它的文本輸入。用C來(lái)編寫C,這就是自舉,假如編譯器是用別的語(yǔ)言寫的,也許你心里就好受一些了。 其實(shí)只要所使用的語(yǔ)言具有一定的寫文件功能就能夠?qū)懢幾g器,為什么這么說(shuō)呢?因?yàn)榫幾g器本身是程序,程序本身是由操作系統(tǒng)加載執(zhí)行的,操作系統(tǒng)識(shí)別程序的格式后按照格式讀取程序中的段并加載到內(nèi)存,最后使程序計(jì)數(shù)器(寄存器pc或ip)跳到程序入口,該程序就執(zhí)行了。 因此用來(lái)編寫編譯器的語(yǔ)言只要具有一定程度的寫文件的能力即可,比如至少要具有形同seek的文件定位功能,這可用于按照不同格式的協(xié)議在不同的偏移處寫入數(shù)據(jù),因此用Python是可以寫出C編譯器的。在這之前我寫過(guò)《操作系統(tǒng)真象還原》一書(shū),里面的第0章第0.17小節(jié)“先有的語(yǔ)言還是先有的編譯器,第1個(gè)編譯器是怎么產(chǎn)生的”,詳細(xì)地說(shuō)明C編譯器是如何自舉的,下面我把它貼過(guò)來(lái)。 首先肯定的是先有的編程語(yǔ)言,哪怕這個(gè)語(yǔ)言簡(jiǎn)單到只有一個(gè)符號(hào)。先是設(shè)計(jì)好語(yǔ)言的規(guī)則,然后編寫能夠識(shí)別這套規(guī)則的編譯器,否則若沒(méi)有語(yǔ)言規(guī)則作為指導(dǎo)方向,編譯器的編寫將無(wú)從下筆。第1個(gè)編譯器是怎么產(chǎn)生的,這個(gè)問(wèn)題我并沒(méi)有求證,不過(guò)可以談下自己的理解,請(qǐng)大伙兒辯證地看。 這個(gè)問(wèn)題屬于哲學(xué)中雞生蛋,蛋生雞的問(wèn)題,這種思維回旋性質(zhì)的本源問(wèn)題經(jīng)常讓人產(chǎn)生迷惑??墒乾F(xiàn)實(shí)生活中這樣的例子太多了,具體如下。 (1)英語(yǔ)老師教學(xué)生英語(yǔ),學(xué)生成了英語(yǔ)老師后又可以教其他學(xué)生英語(yǔ)。 (2)寫新的書(shū)需要參考其他舊書(shū),新的書(shū)將來(lái)又會(huì)被更新的書(shū)參考,就像本書(shū)編寫過(guò)程一樣,要參考許多前輩的著作。 (3)用工具可以制造工具,被制造出來(lái)的工具將來(lái)又可以制造新的工具。 (4)編譯器可以編譯出新的編譯器。 這種自己創(chuàng)造自己的現(xiàn)象,稱為自舉。 自舉?是不是自己把自己舉起來(lái)?是的,人是不能把自己舉起來(lái)的,這個(gè)詞很形象地描述了這類“后果必須有前因”的現(xiàn)象。 以上前3個(gè)舉的都是生活例子,似乎比第4個(gè)更容易接受。即使這樣,對(duì)于前3個(gè)例子大家依然會(huì)有疑問(wèn): (1)第一個(gè)會(huì)英語(yǔ)的人是誰(shuí)教的? (2)第一本書(shū)是怎樣產(chǎn)生的? (3)第一個(gè)工具是如何制造出來(lái)的? 其實(shí)看到第(2)個(gè)例子大家就可能明白了。世界上的第一本書(shū),它的知識(shí)來(lái)源肯定是人的記憶,通過(guò)向個(gè)人或群眾打聽(tīng),把大家都認(rèn)同的知識(shí)記錄到某個(gè)介質(zhì)上,這樣第一本書(shū)就出生了。此后再記錄新的知識(shí)時(shí),由于有了這本書(shū)的參考,不需要重新再向眾人打聽(tīng)原有知識(shí)了,從此以后便形成了書(shū)生書(shū)的因果循環(huán)。 從書(shū)的例子可以證明,本源問(wèn)題中的第一個(gè),都是由其他事物創(chuàng)建出來(lái)的,不是自己創(chuàng)造的自己。 就像先有雞還是先有蛋一樣,一定是先有的其他生命體,這個(gè)生命體不是今天所說(shuō)的雞。伴隨這個(gè)生命體漫長(zhǎng)的進(jìn)化中,突然有一天具備了生蛋的能力(也許這個(gè)蛋在最初并不能孵化成雞,這個(gè)生命體又經(jīng)過(guò)漫長(zhǎng)的進(jìn)化,最終可以生出能夠孵化成雞的蛋),于是這個(gè)蛋可以生出雞了。過(guò)了很久之后,才有的人類。人一開(kāi)始便接觸的是現(xiàn)在的雞而不知道那個(gè)生命體的存在,所以人只知道雞是由蛋生出來(lái)的。 很容易讓人混淆的是編譯C語(yǔ)言時(shí),它先是被編譯成匯編代碼,再由匯編代碼編譯為機(jī)器碼,這樣很容易讓人誤以為一種語(yǔ)言是基于一種更底層的語(yǔ)言的。似乎沒(méi)有匯編語(yǔ)言,C語(yǔ)言就沒(méi)有辦法編譯一樣。拿gcc來(lái)說(shuō),其內(nèi)部確實(shí)要調(diào)用匯編器來(lái)完成匯編語(yǔ)言到機(jī)器碼的翻譯工作。因?yàn)橐呀?jīng)有了匯編語(yǔ)言編譯器,那何必浪費(fèi)這個(gè)資源不用,自己非要把C語(yǔ)言直接翻譯成機(jī)器碼呢,畢竟匯編器已經(jīng)無(wú)比健壯了,將C直接變成機(jī)器碼這個(gè)難度比將C語(yǔ)言翻譯為匯編語(yǔ)言大多了,這屬于重新造輪子的行為。 曾經(jīng)我就這樣問(wèn)過(guò)自己,PHP解釋器是用C語(yǔ)言寫的,C編譯器是用匯編語(yǔ)言寫的(這句話不正確),匯編語(yǔ)言是誰(shuí)寫的呢?后來(lái)才知道,編譯器gcc其實(shí)是用C語(yǔ)言寫的。乍一聽(tīng),什么?用C語(yǔ)言寫C編譯器?自己創(chuàng)造自己,就像電影《超驗(yàn)駭客》一樣。當(dāng)時(shí)的思維似乎陷入了死循環(huán)一樣,現(xiàn)在看來(lái)這不奇怪。其實(shí)編譯器用什么語(yǔ)言寫是無(wú)所謂的,關(guān)鍵是能編譯出指令就行了。 編譯出的可執(zhí)行文件是要寫到磁盤上的,理論上,某個(gè)進(jìn)程,無(wú)論其是不是編譯器,只要其關(guān)于讀寫文件的功能足夠強(qiáng)大,可以往磁盤上寫任意內(nèi)容,都可以生成可執(zhí)行文件,直接讓操作系統(tǒng)加載運(yùn)行。想象一下,用Python寫一個(gè)腳本,功能是復(fù)制一個(gè)二進(jìn)制可執(zhí)行文件,新復(fù)制出來(lái)的文件肯定是可以執(zhí)行的。那Python腳本直接輸出這樣的一個(gè)二進(jìn)制可執(zhí)行文件,它自然就是可以直接執(zhí)行的,完全脫離Python解釋器了。 編譯器其實(shí)就是語(yǔ)言,因?yàn)榫幾g器在設(shè)計(jì)之初就是先要規(guī)劃好某種語(yǔ)言,根據(jù)這個(gè)語(yǔ)言規(guī)則來(lái)寫合適的編譯器。所以說(shuō),要發(fā)明一種語(yǔ)言,關(guān)鍵是得寫出與之配套的編譯器,這兩者是同時(shí)出來(lái)的。最初的編譯器肯定是簡(jiǎn)單、粗糙的,因?yàn)楫?dāng)時(shí)的編程語(yǔ)言肯定不完善,頂多是幾個(gè)符號(hào)而已,所以難以稱之為語(yǔ)言。只有功能完善且符合規(guī)范,有自己一套體系后才能稱之為語(yǔ)言。 不用說(shuō),這個(gè)最初的編譯器肯定無(wú)法編譯今天的C語(yǔ)言代碼。編程語(yǔ)言只是文本,文本只是用來(lái)看的,沒(méi)有執(zhí)行能力。最初的編譯器肯定是用機(jī)器碼寫出來(lái)的。這個(gè)編譯器能識(shí)別文本,可以處理一些符號(hào)關(guān)鍵字。隨著符號(hào)越來(lái)越多,不斷地去改進(jìn)這個(gè)編譯器就是了。 以上的符號(hào)說(shuō)的就是編程語(yǔ)言。后來(lái)這個(gè)編譯器支持的關(guān)鍵字越來(lái)越多了,也就是這個(gè)編譯器支持的編程語(yǔ)言越發(fā)強(qiáng)大了,可以寫出一些復(fù)雜的功能的時(shí)候,干脆直接用這個(gè)語(yǔ)言寫個(gè)新的編譯器,這個(gè)新的編譯器出生時(shí),還是需要用舊的編譯器編譯出來(lái)的。 只要有了新的編譯器,之后就可以和舊的編譯器說(shuō)拜拜了。發(fā)明新的編譯器實(shí)際上就是能夠處理更多的符號(hào)關(guān)鍵字,也就是又有新的開(kāi)發(fā)語(yǔ)言了,這門語(yǔ)言可以是全新的也可以是最初的語(yǔ)言,這取決于編譯器的實(shí)現(xiàn)。這個(gè)過(guò)程不斷持續(xù),不斷進(jìn)化,逐漸才有了今天的各種語(yǔ)言解釋器,這是個(gè)迭代的過(guò)程。 圖 0-1 圖0-1在網(wǎng)絡(luò)上非?;?,它常常與勵(lì)志類的文字相關(guān)。起初看到這個(gè)雕像在雕刻自己時(shí),我著實(shí)被感動(dòng)了,感受到的是一種成長(zhǎng)之痛。今天把它貼過(guò)來(lái)的目的是想告訴大家,起初的編譯器也是功能簡(jiǎn)單,不成規(guī)范的,然而經(jīng)過(guò)不斷自我“雕刻”,它才有了今天功能的完善。 下面的內(nèi)容我參考了別人的文章,由于找不到這位大師的署名,只好在此先獻(xiàn)上我真摯的敬意,感謝他對(duì)求知者的奉獻(xiàn)。 要說(shuō)到C編譯器的發(fā)展,必須要提到這兩位大神—C語(yǔ)言之父Dennis Ritchie和Ken Thompson。Dennis和Ken在編程語(yǔ)言和操作系統(tǒng)的深遠(yuǎn)貢獻(xiàn)讓他們獲得了計(jì)算機(jī)科學(xué)的最高榮譽(yù),Dennis和Ken于1983年贏得了ACM圖靈獎(jiǎng)。 編譯器是靠不斷學(xué)習(xí),不斷積累才發(fā)展起來(lái)的,這是自我學(xué)習(xí)的過(guò)程。下面來(lái)看看他們是如何讓編譯器長(zhǎng)大的。 我們都知道轉(zhuǎn)義字符,轉(zhuǎn)義字符是以\開(kāi)頭的多個(gè)字符,通常表示某些控制字符,它們通常是不可鍵入的,也就是這些字符無(wú)法在鍵盤上直接輸入,比如\n表示回車換行,\t表示tab。由于以\開(kāi)頭的字符表示轉(zhuǎn)義,因此要想表示\字符本身,就約定用\來(lái)轉(zhuǎn)義自己,即\\表示字符\。轉(zhuǎn)義字符雖然表示的是單個(gè)字符的意義,在編譯器眼里轉(zhuǎn)義字符是多個(gè)字符組成的字符串,比如\n是字符\和n組成的字符串。 起初的C編譯器中并沒(méi)有處理轉(zhuǎn)義字符,為敘述方便,我們現(xiàn)在稱之為舊編譯器。如果待編譯的代碼文件中有字符串\\,這在舊編譯器眼里就是\\字符串,并不是轉(zhuǎn)義后的單個(gè)字符\。為了表明編譯器與作為其輸入的代碼文件的關(guān)系,我們稱“作為輸入的代碼文件”為應(yīng)用程序文件。盡管被編譯的代碼文件是實(shí)現(xiàn)了一個(gè)編譯器,而在編譯器眼里,它只是一個(gè)應(yīng)用程序級(jí)的角色。例如,gcc –c a.c中,a.c就是應(yīng)用程序文件。 現(xiàn)在想在編譯器中添加對(duì)轉(zhuǎn)義字符的支持,那就需要修改舊編譯器的源代碼,假設(shè)舊編譯器的源代碼文件名為compile_old.c。被修改后的編譯器代碼,已不屬于舊編譯器的源代碼,故我們命名其文件名為compile_new_a.c,圖0-2是修改后的內(nèi)容。 代碼compile_new_a.c 圖 0-2 其中,函數(shù)next()的功能是返回待處理文本(即被編譯的源碼文件)中的下一字符,強(qiáng)調(diào)一下是“單個(gè)字符”,并不是記法分析中的單詞(即token)。 用舊編譯器將新編譯器的源代碼compile_new_a.c編譯,生成可執(zhí)行文件,該文件就是新的編譯器,我們?nèi)∶麨樾戮幾g器_a。為了方便理清它們的關(guān)系,將它們列入表0-1中。 表 0-1 這下編譯出來(lái)的新編譯器_a可以編譯含有轉(zhuǎn)義字符\\的應(yīng)用程序代碼了,也就是說(shuō),待編譯的文件(也就是應(yīng)用程序代碼)中,應(yīng)該用\\來(lái)表示\。而單獨(dú)的字符\在新編譯器_a中未做處理而無(wú)法通過(guò)編譯。所以此時(shí)新編譯器_a是無(wú)法編譯自己的源代碼compile_new_a.c的,因?yàn)樵撛次募兄皇菃蝹€(gè)\字符,新編譯器_a只認(rèn)得\\。先更新它們的關(guān)系,見(jiàn)表0-2。 表 0-2 也就是說(shuō),現(xiàn)在新編譯器_a,無(wú)法編譯自己的源文件compile_new_a.c,只有舊編譯器才能編譯它。再說(shuō)一下,新編譯器_a無(wú)法正確編譯自己的源文件compile_new_a.c的原因是,compile_new_a.c中\(zhòng)字符應(yīng)該用轉(zhuǎn)義字符的方式來(lái)引用,即所有用\的地方都應(yīng)該替換為\\。再回頭看一下新編譯器_a的源代碼compile_new_a.c,它只處理了字符串\\,單個(gè)\沒(méi)有對(duì)應(yīng)的處理邏輯。下面修改代碼,將新修改后的代碼命名為compile_new_b.c,如圖0-3所示。 代碼compile_new_b.c 圖 0-3 其實(shí)compile_new_b.c只是更新了轉(zhuǎn)義字符的語(yǔ)法,這是新編譯器_a所支持的新的語(yǔ)法,下面還是以新編譯器_a來(lái)編譯新的編譯器。 用新編譯器_a編譯此文件,將生成新編譯器_b,將新的關(guān)系錄入到表0-3中。 表 0-3 繼續(xù)之前再說(shuō)一下:用編譯器去編譯另一編譯器的源碼,也許有的讀者覺(jué)得很費(fèi)解,其實(shí)你把“被編譯的編譯器源碼”當(dāng)成普通的應(yīng)用程序源碼就特別容易理解了。上面的編譯器代碼compile_new_b.c,其第3、6、7行的字符串\\被新編譯器_a處理后,會(huì)以單字符\來(lái)代替(這是新編譯器_a源碼中return語(yǔ)句的功能),因此最終處理完成后的代碼等同于代碼compile_new_a.c?,F(xiàn)在想加上換行符\n的支持,如圖0-4所示。 圖 0-4 由于現(xiàn)在編譯器還不認(rèn)識(shí)\n,故這樣做肯定不行,不過(guò)可以用其ASCII碼來(lái)代替,將其命名為compile_new_c.c,如圖0-5所示。 代碼compile_new_c.c 圖 0-5 用新編譯器_a來(lái)編譯compile_new_c.c,將生成新編譯器_c,新編譯器_c的代碼相當(dāng)于代碼compile_new_c.c中所有\(zhòng)\被替換為\后的樣子,如表0-4所列,暫且稱之為代碼compile_new_c1.c,如圖0-6所示。 代碼compile_new_c1.c 圖 0-6 表 0-4 最后再修改compile_new_c.c為compile_new_d.c,將10用\n替代,如圖0-7所示。 代碼compile_new_d.c 圖 0-7 用新編譯器_c編譯compile_new_d.c,生成新編譯器d,將直接識(shí)別\n。同理,新編譯器d的代碼相當(dāng)于代碼compile_new_d.c中,所有字符串\\被替換為字符\、字符\n被替換為數(shù)字10后的樣子,即等同于代碼compile_new_c1.c,如表0-5所列。 編譯器經(jīng)過(guò)這樣不斷地訓(xùn)練,功能越來(lái)越強(qiáng)大,不過(guò)占用的空間也越來(lái)越大了。 表 0-5 《自制編程語(yǔ)言》 鄭鋼 著 本書(shū)全面從腳本語(yǔ)言和虛擬機(jī)介紹開(kāi)始,講解了詞法分析的實(shí)現(xiàn)、一些底層數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)、符號(hào)表及類的結(jié)構(gòu)符號(hào)表,常量存儲(chǔ),局部變量,模塊變量,方法存儲(chǔ)、虛擬機(jī)原理、運(yùn)行時(shí)棧實(shí)現(xiàn)、編譯的實(shí)現(xiàn)、語(yǔ)法分析和語(yǔ)法制導(dǎo)自頂向下算符優(yōu)先構(gòu)造規(guī)則、調(diào)試、查看指令流、查看運(yùn)行時(shí)棧、給類添加更多的方法、垃圾回收實(shí)現(xiàn)、添加命令行支持命令行接口。 《今日互動(dòng)》 你最想從這本書(shū)中學(xué)到什么?為什么?截止時(shí)間7月27日17時(shí),留言+轉(zhuǎn)發(fā)本活動(dòng)到朋友圈,小編將抽獎(jiǎng)選出2名讀者贈(zèng)送紙書(shū)2本,文末留言點(diǎn)贊最多的自動(dòng)獲得圖書(shū)1本(參與活動(dòng)直達(dá)微信端編程達(dá)到一個(gè)高的境界就是自制腳本語(yǔ)言) |
|