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