知乎上有人提問:電腦怎樣執行編程語言的?

很多剛剛入坑的小白可能對此完全沒有概念,或者模模糊糊知道個大概,我們寫下的一行行代碼,計算機到底是如何在執行的呢?
我們以x86架構的CPU為研究對象,從一個例子出發,來嘗試解答這個問題。
1、高級語言為了方便編程,偉大的計算機先驅們發明了一個又一個的編程語言,使得我們可以用人類最容易理解的語法規則去告訴計算機完成我們想要的功能。
比如,一個C語言程序員寫下了一行代碼:
intsum=a+b;一句簡單的不能再簡單的C語言語句。
但即便是如此簡單,聰明絕頂的計算機卻還是看不懂:這是弄啥捏?
這時候就需要一個翻譯,負責把人類編寫的高級語言「翻譯」成計算機能看得懂的東西,這個翻譯就是編譯器。
2、編譯鏈接上面的高級語言語句經過編譯器編譯鏈接後,生成了一個目標運行平台為x86架構的可執行程序exe/elf,使用反編譯工具IDA進行分析,可以看到這行代碼編譯後的樣子是這樣的:

mov eax, a : 將變量a的值存入eax寄存器中
add eax, b : 把變量b的值和eax寄存器的值相加,並將結果保存在eax寄存器中
mov sum, eax : 將計算結果從eax寄存器寫入sum變量
看到了嗎,就像把大象關進冰箱需要分三步,計算機完成程序員的一條加法語句,也分了三步:取出被加數、加上加數、寫入結果。
3、機器指令上面的匯編指令只是為了人類理解方便的助記符,計算機同樣也不認識這玩意,那幾條指令在內存中實際上是這樣的一串數據:
十六進制:
8B 45 EC 03 45 E0 89 45 F8
十六進制是為了書寫方便,計算機真正能看到的只有二進制的比特流:
10001011 01000101 11101100 00000011 01000101 11100000 10001001 01000101 11111000
接下來,計算機要做的事情就是識別這些二進制流都是什麼意思,轉換成一條條的指令來執行。
在開始執行之前,先來了解一下指令格式。
4、指令格式x86架構CPU指令集中的指令格式如下:

主要有六個部分:
需要注意的是,並不是每一條指令都包含上面的所有部分,許多指令只包含其中一部分字段。
根據操作碼的長度不同,指令分為單字節操作碼指令、雙字節操作碼指令、三字節操作碼指令。
5、執行指令計算機中真正負責指令執行的核心部件是中央處理器CPU,在CPU中有一個指令寄存器IP,全稱是Instruction Pointer,在32位下,它叫EIP,在64位下它叫RIP。
下面開始執行:
指令寄存器EIP指向了第一條指令,開始讀取第一個字節:10001011,也就是0x8B。
開始指令譯碼,翻譯出這是一條什麼指令。
下面是x86架構的CPU指令操作碼錶:

CPU中的指令譯碼模塊拿到手一看,呀,不是指令前綴,是個單字節操作碼的mov指令,要往eax寄存器裡面塞數據,數據從哪來呢?
再往後一看,0x45,再來譯碼:

好傢夥,原來是根據ebp寄存器的值+一個8位的偏移來讀取數據。
再往後讀取一個字節,就是偏移值:EC。
現在第一條指令就譯碼出來了:將ebp+0xEC位置處的4個字節的數據取出來,放到eax寄存器中。,這就是這一條指令要幹的事情。
同時CPU還得出了另一個信息:這一條指令長度是3個字節,下一條指令的起始地址是在3個字節之後,隨後,指令寄存器EIP向後撥動,指向下一條指令的地址:$+3。
指令譯碼完成之後,開始來正式執行它。
執行完一條以後,又來到指令寄存器EIP指向的地方,隨後再次指令譯碼、執行,不斷重複這個過程,依次執行每一條指令。
這其實就是CPU工作最基本的原理。
拓展上面描述的過程是CPU在硬件電路層面完成的,但這種設計思想在軟件領域也同樣適用。
大家如果去研究Java虛擬機JVM和Python的解釋器源代碼時,也會發現有相似之處:JVM和解釋器通過定義一套自己的「指令集」,然後它們的編譯器使用這套指令集將Java和Python代碼編譯成對應的程序。
運行的時候也類似,虛擬機或者解釋器不斷識別每一條指令,譯碼、執行,和CPU執行指令的過程頗有幾分相似。

C/C++語言編譯的程序,最後是直接編譯成了CPU的指令,所以跨平台能力差,如果換到ARM架構平台,原來的程序將無法執行,需要重新編譯成新的平台的程序。
而Java、Python這類語言,是自己在軟件層面的指令集,因為其自身已經開發了針對不同CPU平台的虛擬機、解釋器,所以這些語言編寫的程序移植性好,真正做到一次編寫,到處運行。
總結我們使用高級語言C、C++編寫的程序代碼,經過編譯器的編譯鏈接,最終變成CPU可以理解的機器指令,隨後CPU在執行時通過不斷的譯碼、執行,最終實現高級語言所描述的功能。
現在你知道你用編程語言寫下的程序是如何跑起來的了嗎?