程序是怎么一步步變成機(jī)器指令的?
大家好,我是小風(fēng)哥,今天簡單聊聊程序是怎么一步步變成機(jī)器指令的。
左邊是我們寫的代碼,右邊是CPU執(zhí)行的機(jī)器指令:
圖片
想讓CPU執(zhí)行代碼只需要簡單的點(diǎn)擊一下這個(gè)按鈕:
圖片
可是你知道這個(gè)按鈕的背后經(jīng)歷了哪些復(fù)雜的操作,你有沒有想過代碼是怎么一步步變成機(jī)器指令的?
程序員編寫的程序?qū)嶋H上就是一個(gè)字符串,必須得有個(gè)什么東西把字符串轉(zhuǎn)變從機(jī)器指令,它的輸入是字符串,輸出是01二進(jìn)制機(jī)器指令,這就是編譯器。
圖片
編譯器本身就是一個(gè)程序,把人類認(rèn)識(shí)的程序轉(zhuǎn)為CPU可以執(zhí)行的機(jī)器指令。
假設(shè)有這樣一段代碼:
圖片
這實(shí)際上就是一個(gè)字符串,編譯器要做的第一件事就是遍歷字符串并把有意義的字符組合提取出來,忽略掉空格換行等字符。
這里每一個(gè)字符組合實(shí)際上都有類型,比如int 和main都是關(guān)鍵字,0和5都是數(shù)字等,因此還需要標(biāo)注好類型,這一步就是所謂的提取token。
圖片
提取出token之后還需要知道這些token組合在一起的含義是什么。
接下來遍歷所有token進(jìn)行解析。
按照什么解析呢?答案是按照語法。
圖片
假設(shè)編譯器接下來發(fā)現(xiàn)token是if,那么很顯然,接下來會(huì)判定這是一個(gè)if語句,那么接下來就按照if語句的語法來解析。
圖片
編譯器在按照語法解析時(shí)會(huì)生成一顆樹,首先匹配的是if本身:
圖片
接下來是左括號(hào):
圖片
括號(hào)之后是布爾表達(dá)式:
圖片
布爾表達(dá)式之后是右括號(hào)以及大的左括號(hào)。
接著是if內(nèi)部的語句:
圖片
注意看,根據(jù)語法解析token后生成的這棵樹就叫做抽象語法樹:AST。
接下來,編譯器遍歷這顆抽象語法樹并生成指令:
圖片
當(dāng)然真正的編譯器可能并不會(huì)在這里直接生成機(jī)器指令。
我們知道CPU只能執(zhí)行一種類型的機(jī)器指令,x86處理器只能執(zhí)行x86機(jī)器指令,arm處理器只能執(zhí)行arm機(jī)器指令:
圖片
如果你發(fā)明了一種語言,為了適配不同的處理器自己需要針對每一種處理器編寫相應(yīng)的后端部分。
圖片
要是有一種工具能幫我們完成針對不同處理器的適配工作就好了,這就是LLVM,我們可以只生成針對LLVM的中間代碼,由LLVM處理剩下的部分。
圖片
這就是生成中間代碼的好處。
值得注意的是,編譯器在生成指令時(shí)會(huì)進(jìn)行優(yōu)化,這個(gè)示例中變量a實(shí)際上沒什么用處,編譯器會(huì)注意到這一點(diǎn)并把針對變量a的賦值指令去掉。
圖片
得到匯編指令后編譯器會(huì)最終將其轉(zhuǎn)為CPU可以認(rèn)知的二進(jìn)制機(jī)器指令,每個(gè)源文件被編譯后都會(huì)生成一個(gè)目標(biāo)文件,目標(biāo)文件中就是轉(zhuǎn)換后的二進(jìn)制機(jī)器指令。
圖片
最后,鏈接器會(huì)把目標(biāo)文件打包成最終的可執(zhí)行程序,