计算机内部程序代码,计算机为什么能够读懂程序代码?
01 引子
上一回,我們的主人公小A初次亮相,憑借基礎(chǔ)的前后端理解,從技術(shù)實(shí)現(xiàn)的層面為我們剖析了微信掃碼登錄的原理和機(jī)制。可能很多人因此會好奇,小A到底是做什么的呢?為什么能夠弄懂這些原理呢?
其實(shí),小A是一名業(yè)余碼農(nóng)。為什么要叫業(yè)余碼農(nóng)呢,是因?yàn)樗X得自己屬于半路出家,很多計(jì)算機(jī)基礎(chǔ)思想都不夠?qū)I(yè),還有很大的進(jìn)步空間,因此稱自己為業(yè)余碼農(nóng)。
但是興趣總是最好的老師,這不,小A正又盯著屏幕上的幾行代碼發(fā)愁:#include int main(){std::cout << "Hello World!" << std::endl;return 0;}
編譯并運(yùn)行這段 C++ 代碼就能夠完美打印出Hello World!,似乎沒啥毛病呀!
“計(jì)算機(jī)是怎么知道我敲的這些代碼的意思呢?”小A 苦皺著眉頭,喃喃道。原來,我們的業(yè)余碼農(nóng)小A 是沒想明白計(jì)算機(jī)是如何將這些一串串的字符轉(zhuǎn)變成計(jì)算機(jī)能夠執(zhí)行的機(jī)器碼的,這其實(shí)不就是編譯原理嘛。
小A 回想起之前上過的數(shù)電模電課,知道計(jì)算機(jī)的世界里都是數(shù)字化的,也就是說計(jì)算機(jī)只知道二進(jìn)制?0 和 1 。不同數(shù)量 01 的組合在計(jì)算機(jī)的內(nèi)部構(gòu)成了不同的指令,而不同指令的組合又構(gòu)成了不同的操作。
這就好比流水線的生產(chǎn)模式,假如把計(jì)算機(jī)看作一條流水線,那么在這條流水線上有不同的工位,每一個(gè)工位代表著不同的指令。生產(chǎn)不同的產(chǎn)品就需要不同工位的一同參與,可能按順序執(zhí)行,也有可能并列執(zhí)行。
想到這,小A意識到其實(shí)這些由 0?和 1 構(gòu)成的指令應(yīng)該就是計(jì)算機(jī)能夠執(zhí)行的機(jī)器碼。不過那這些機(jī)器碼好像與上面的 C++?代碼還相差甚遠(yuǎn),中間肯定是經(jīng)歷了一系列的轉(zhuǎn)換。嗯?這個(gè)過程有點(diǎn)像是翻譯的過程,好像是將程序代碼翻譯成了機(jī)器碼!
小A 茅塞頓開,好像又找回了之前英語四級怒考 605 分的自信。看來,英語沒白學(xué)!
計(jì)算機(jī)理解程序代碼的過程是不是就像是將英文翻譯成了另一種語言呢?一想到英語的那些高階語法,小A 就開始忍不住頭疼,“不會這編程還得學(xué)個(gè)什么時(shí)態(tài)轉(zhuǎn)換語態(tài)切換從句倒裝吧...”。
不過頭疼歸頭疼,該學(xué)的還是得耐著性子學(xué)。小A 知道,在計(jì)算機(jī)真正運(yùn)行 C++?程序代碼之前,還需要經(jīng)過復(fù)雜的編譯過程,這個(gè)編譯過程似乎對計(jì)算機(jī)理解程序代碼起著關(guān)鍵性作用。
02 C++編譯過程
找到了分析問題的方向,小A 迫不及待的到處查詢 C++ 編譯過程到底是如何發(fā)生的。他發(fā)現(xiàn) C++ 的整個(gè)編譯過程包含多項(xiàng)操作,主要可分為四個(gè)階段:1.編譯預(yù)處理
2.編譯優(yōu)化階段
3.匯編過程
4.鏈接過程
這四個(gè)階段按順序執(zhí)行,每一個(gè)階段分別處理上一個(gè)階段的輸出代碼,并輸入下一個(gè)階段。每個(gè)階段的作用分別為:
0x00 編譯預(yù)處理
讀取 C++ 源代碼,對其中的偽指令和特殊符號進(jìn)行處理。這個(gè)預(yù)處理實(shí)際上可看作是將源程序中的一些特殊指令或者符號進(jìn)行替換。經(jīng)過預(yù)處理的替換,就會生成一個(gè)沒有特殊指令、沒有特殊符號的輸出文件。這個(gè)文件的含義和源文件本質(zhì)上是相同的,但內(nèi)容和表達(dá)方式有所不同。特殊指令:稱為偽指令,包括宏定義指令、條件編譯指令、頭文件包含指令。比如上述 C++ 代碼中第一行的 #include 就是頭文件包含指令,會在編譯預(yù)處理階段被替換。
0x01 編譯優(yōu)化階段
經(jīng)過預(yù)編譯后的輸出文件會經(jīng)過編譯優(yōu)化階段,將原始代碼轉(zhuǎn)化為匯編語言。這個(gè)階段是整個(gè)編譯過程的核心,也是起到 “ 翻譯 ” 作用的關(guān)鍵。整個(gè)階段的工作過程一般可分為六個(gè)步驟:1.詞法分析
2.語法分析
3.語義分析
4.中間代碼生成
5.代碼優(yōu)化
6.目標(biāo)代碼生成
在進(jìn)行編譯時(shí),會經(jīng)過詞法分析、語法分析和語義分析將高級語言代碼一步步分解剖析,按照定義的語法將不同的代碼語句拆解,并根據(jù)一些標(biāo)準(zhǔn)來對代碼語句進(jìn)行分析檢查,最后生成中間形式的代碼用于優(yōu)化。而優(yōu)化步驟則是對中間代碼進(jìn)行優(yōu)化改進(jìn),力圖提升生成的匯編代碼的效率。
0x02 匯編過程
匯編語言可看做是一種低級語言,十分接近于機(jī)器碼的實(shí)現(xiàn)。匯編語言:用于硬件底層編程的低級語言,常用助記符代替機(jī)器指令,用地址符號或標(biāo)號代替指令或操作數(shù)的地址。特定的匯編語言和特定的機(jī)器語言指令集一一對應(yīng),通過匯編過程轉(zhuǎn)換成機(jī)器指令。
由此可見,匯編過程實(shí)際上就是將匯編語言翻譯成為了機(jī)器碼,這些機(jī)器碼就是 C++?源代碼的底層表達(dá),理論上計(jì)算機(jī)可以通過執(zhí)行這些機(jī)器碼來實(shí)現(xiàn)對源代碼的運(yùn)行。
0x03 鏈接過程
但是要知道,一個(gè)普通的高級語言程序,都不單單只包含一個(gè)文件。可能某個(gè)源文件就會調(diào)用其它庫文件中的函數(shù)或者其它源文件中定義的符號函數(shù)等。因此多個(gè)文件在經(jīng)過編譯匯編之后,還需要通過鏈接過程將不同的目標(biāo)文件連接起來,建立起引用和調(diào)用的聯(lián)系。直至這步完成之后,程序語言代碼才能夠真正意義上的被計(jì)算機(jī)理解和運(yùn)行。
反復(fù)思索 C++?編譯的整個(gè)過程,小A 感覺那幾行簡潔的代碼仿佛經(jīng)過了千錘百煉一般,雖然最終似乎面目全非,但是卻變成了最原始最純潔的樣子。
小A 忍不住一陣感嘆整個(gè)編譯過程的環(huán)環(huán)相扣以及精巧絕倫,同時(shí)對編譯階段的原理產(chǎn)生了更大的興趣。
03 編譯原理
編譯階段的過程是通過編譯器所實(shí)現(xiàn)的,編譯器通過六個(gè)步驟將由數(shù)字、字符串以及一些關(guān)鍵字組成的字符流進(jìn)行解析,最后經(jīng)過優(yōu)化生成匯編代碼。
圖 一個(gè)編譯器的各個(gè)步驟
那是如何進(jìn)行解析的呢?小A這時(shí)候想到了中英文中的主謂賓結(jié)構(gòu),難道也可以把程序代碼劃分為主語、謂語、賓語嗎?不妨舉個(gè)栗子來分析好了,小A 熟練的寫下了一行代碼:position = initial + rate * 60
不如就來分析這一行賦值語句的翻譯過程吧。
0x00 詞法分析
最先輸入編譯器的是源程序代碼的字符流,如上述例子所示的是由英文、符號和數(shù)字組成的字符串。詞法分析的過程就是將字符流中有意義的詞或符號進(jìn)行提取并分類表示,同時(shí)保存在符號表中,并映射為『詞法單元』。
比方說上述代碼中的詞position,可映射為詞法單元。id 表示的是標(biāo)志符(identifier),而 1 表示符號表中的第一個(gè)條目。
但是,符號=卻不會保存在符號表中,因?yàn)槠洳痪哂兄档母拍?#xff0c;只是一個(gè)賦值符號。所以其對應(yīng)的詞法單元直接用它本身來表示<=>。
對上述代碼所有詞及符號進(jìn)行詞法分析后,可獲得詞法單元:詞及符號詞法單元position
=< = >
initial
+< + >
rate
*< * >
60<60>
對應(yīng)的符號表為\\\1position...
2initial...
3rate...
因此該上述賦值語句代碼可用詞法單元表示:<=><60>
這樣一來,通過詞法分析就把代碼語句給剝離抽象化,清晰的展現(xiàn)出語句的結(jié)構(gòu)性。
0x01 語法分析
語法分析,故名思義就是檢查語言的表述是否符合已經(jīng)設(shè)定的語法規(guī)則。而在語法分析器中,這樣的規(guī)則稱之為『文法』。文法:通過集合來描述語法結(jié)構(gòu)的規(guī)則。如主謂賓結(jié)構(gòu)就可看作一種文法。
每一種編程語言都有其對應(yīng)的文法,根據(jù)制定的文法規(guī)則可以對詞法分析產(chǎn)生的詞法單元串進(jìn)行解析。文法解析的方法有多種,優(yōu)劣勢不一,但目的都是為了構(gòu)建一顆語法分析樹。這同時(shí)也是語法分析階段輸出的結(jié)果。
對于上述賦值語句而言,根據(jù)不同運(yùn)算符的執(zhí)行順序,將賦值運(yùn)算符=作為根節(jié)點(diǎn),可得到語法分析樹:
獲得語法分析樹之后,整個(gè)代碼結(jié)構(gòu)用樹的形式進(jìn)行表示,從而方便后續(xù)進(jìn)一步對源程序進(jìn)行分析。
0x02 語義分析
語義分析是使用語法樹和符號表中的信息來檢查源程序是否和語言定義的語義一致。如果說語法的分析是對程序語句的結(jié)構(gòu)進(jìn)行分析,那么語義分析則是對語句的邏輯性和合理性進(jìn)行分析。比方說:語句:猴子是程序員
語法分析得到主謂賓結(jié)構(gòu),『猴子』是主語,『是』是謂語,『程序員』是賓語。從語法上來說并沒有錯(cuò)誤。
但是很明顯,語義上是有問題的。
因此在語義分析環(huán)節(jié)很重要的部分就是對程序語句進(jìn)行類型檢查,比方說應(yīng)保證運(yùn)算符兩邊的數(shù)值類型一致。這本質(zhì)就是要檢查出『猴子是程序員』這樣的錯(cuò)誤。
對于上述的賦值語句,假設(shè)position、initial、rate已被聲明為浮點(diǎn)數(shù)類型,那么表面上整數(shù)60應(yīng)與rate的類型不同,在語義分析的時(shí)候就會找出這樣的問題。
只不過在很多語言中允許自動(dòng)類型轉(zhuǎn)換,會將整數(shù)60轉(zhuǎn)換成浮點(diǎn)數(shù)從而滿足語義的要求。因此經(jīng)過語義分析后,語法樹會新增inttofloat節(jié)點(diǎn)以達(dá)到類型轉(zhuǎn)換的目的:
0x03 中間代碼生成
在翻譯源程序的過程中,往往會使用多個(gè)中間表示形式進(jìn)行以方便不同的運(yùn)算處理。一般常用一種稱為『三地址代碼』的中間表示形式將語法樹的結(jié)構(gòu)進(jìn)行改寫。該形式根據(jù)運(yùn)算完成的順序,生成臨時(shí)名字以存放運(yùn)算的值。如上述賦值語句的中間代碼:
t1?=?inttofloat(60)t2?=?id3?*?t1t3?=?id2?+?t2id1?=?t3
0x04 代碼優(yōu)化
代碼優(yōu)化階段試圖改進(jìn)中間代碼,以達(dá)到提高效率或者其它更有優(yōu)勢的目的。優(yōu)化階段會根據(jù)一些既有的規(guī)則去對中間代碼進(jìn)行改進(jìn),不同的編譯器之間往往具有差異性。上述中間代碼可以將inttofloat操作進(jìn)行優(yōu)化,使用浮點(diǎn)數(shù)60.0來代替整數(shù)60從而滿足語義分析。中間代碼優(yōu)化為:
t1?=?id3?*?60.0id1?=?id2?+?t1
0x05 目標(biāo)代碼生成
目標(biāo)代碼的生成是將中間代碼翻譯為匯編語言。在這個(gè)過程中,需要為變量合理地分配寄存器,選擇內(nèi)存位置。之后再根據(jù)匯編語言的操作完成翻譯。上述賦值語句對應(yīng)的匯編代碼為:
LDF?R2,?id3MULF?R2,?R2,?#60.0LDF?R1,?id2ADDF?R1,?R1,?R2STF?id1,?R1
在上面的代碼中,每個(gè)指令的第一個(gè)運(yùn)算分量指定了目標(biāo)地址以存放計(jì)算結(jié)果。這樣的操作已經(jīng)是從硬件層面對數(shù)值操作和運(yùn)算執(zhí)行。之后通過匯編過程即可獲得真正的機(jī)器指令序列。
看到這,小A 已經(jīng)快有些迷糊了。盡管例子里對賦值語句的編譯過程看起來簡單明了,但是一想到其它程序代碼里無數(shù)的關(guān)鍵字、變量和函數(shù)調(diào)用還是忍不住微微嘆了口氣。
畢竟,這些內(nèi)容還只不過《編譯原理》的第一章。真正每一階段的實(shí)現(xiàn)需要考究的東西還有太多。不過學(xué)習(xí)都是循序漸進(jìn)的,學(xué)到這小A 已經(jīng)大致清楚 C++?程序從源代碼到運(yùn)行起來的經(jīng)過了。
04 解釋器
此外,他還發(fā)現(xiàn)一個(gè)彩蛋。原來除了編譯器能夠起到翻譯的作用,還有一種稱作“解釋器”的東西同樣可以起到翻譯作用。
簡單來說,編譯器是將源代碼完整轉(zhuǎn)換為機(jī)器碼;而解釋器是將源代碼直接生成機(jī)器碼并交由硬件執(zhí)行。因此編譯器事先需要將整個(gè)程序編譯成另外的代碼,而解釋器可一行一行讀取程序,然后翻譯執(zhí)行。解釋性語言編譯性語言不生成目標(biāo)程序生成目標(biāo)程序
一邊解釋,一邊執(zhí)行整體編譯,一次執(zhí)行
每個(gè)語句執(zhí)行時(shí)都要進(jìn)行翻譯可只翻譯一次,可多次執(zhí)行
一般程序執(zhí)行速度慢一般程序執(zhí)行速度快
跨平臺性好跨平臺性差
C/C++/elphi等為編譯性語言Python/JavaScript?/ Perl /Shell等為解釋性語言
總結(jié)
以上是生活随笔為你收集整理的计算机内部程序代码,计算机为什么能够读懂程序代码?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux hive mysql_Lin
- 下一篇: mssql与oracle不同点,MySq