指令和運算 - 計算機指令:高級語言是如何翻譯成計算機指令


指令和運算 - 計算機指令:高級語言是如何翻譯成計算機指令

計算機組成原理目錄:https://www.cnblogs.com/binarylei/p/12585607.html

本小節聚焦在高級語言是如何翻譯成機器碼,以及機器碼的格式。之后的兩小節則深入講解 CPU 如何執行條件、循環、函數、遞歸這些完整的語句。

1. 機器碼 vs 計算機指令

計算機只認知 "0" 和 "1",那我們每天用高級語言的程序,最終都會是怎么變成一串串 "0" 和 "1" 的?這一串串 "0" 和 "1" 又是怎么在 CPU 中處理的?今天,我們就來仔細介紹一下,“機器碼”和“計算機指令”到底是怎么回事。

我們常說,CPU(Central Processing Unit,中央處理器) 就是計算機的大腦。

  • 從硬件的角度:CPU 就是一個超大規模集成電路,通過電路實現了加法、乘法乃至各種各樣的處理邏輯。
  • 從軟件的角度:CPU 就是一個執行各種計算機指令(Instruction Code)的邏輯機器。這里的計算機指令,就好比一門 CPU 能夠聽得懂的語言,我們也可以把它叫作機器語言(Machine Language)

不同的 CPU 能夠聽懂的語言不太一樣。比如 Intel CPU 和 ARM CPU 各自支持的語言,就是兩組不同的計算機指令集(Instruction Set)

2. 從編譯到匯編,代碼怎么變成機器碼

了解了計算機指令和計算機指令集,接下來我們來看看,平時編寫的代碼,到底是怎么變成一條條計算機指令,最后被 CPU 執行的呢?我們拿一小段真實的 C 語言程序來看看。

// test.c
int main()
{
  int a = 1; 
  int b = 2;
  a = a + b;
}

要讓這段程序在一個 Linux 操作系統上跑起來,我們需要將 C 語言翻譯計算機指令:

  • 編譯(Compile):把整個程序翻譯成一個匯編語言(ASM,Assembly Language)
  • 匯編(Assembly):針對匯編代碼,再用匯編器(Assembler)翻譯成機器碼(Machine Code)。這些機器碼由 "0" 和 "1" 組成的機器語言表示。這一條條機器碼,就是一條條的計算機指令。這樣一串串的 16 進制數字,就是我們 CPU 能夠真正認識的計算機指令。

在 Linux 操作系統上,我們可以簡單地使用 gcc 和 objdump 這樣兩條命令,把對應的匯編代碼和機器碼都打印出來。

gcc -g -c test.c
objdump -d -M intel -S test.o

可以看到,左側有一堆數字,這些就是一條條機器碼;右邊有一系列的 push、mov、add、pop 等,這些就是對應的匯編代碼。一行 C 語言代碼,有時候只對應一條機器碼和匯編代碼,有時候則是對應兩條機器碼和匯編代碼。匯編代碼和機器碼之間是一一對應的。

test.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
int main()
{
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
  int a = 1; 
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
  int b = 2;
   b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
  a = a + b;
  12:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  15:   01 45 fc                add    DWORD PTR [rbp-0x4],eax
}
  18:   5d                      pop    rbp
  19:   c3                      ret  

從高級語言到匯編代碼,再到機器碼,就是一個日常開發程序,最終變成了 CPU 可以執行的計算機指令的過程。

圖1:從高級語言到匯編代碼,再到機器碼

3. 解析指令和機器碼

日常用的 Intel CPU,有 2000 條左右的 CPU 指令。不過一般來說,常見的指令可以分成五大類。

  1. 算術類指令:我們的加減乘除,在 CPU 層面,都會變成一條條算術類指令。
  2. 邏輯類指令:邏輯上的與或非,都是這一類指令。
  3. 數據傳輸類指令:給變量賦值、在內存里讀寫數據,用的都是數據傳輸類指令。
  4. 條件分支類指令:日常我們寫的“if/else”,其實都是條件分支類指令。
  5. 無條件跳轉指令:寫一些大一點的程序,我們常常需要寫一些函數或者方法。在調用函數的時候,其實就是發起了一個無條件跳轉指令。
圖2:五大類計算機指令

我們說過,不同的 CPU 有不同的指令集,也就對應着不同的匯編語言和不同的機器碼。為了方便你快速理解這個機器碼的計算方式,我們選用最簡單的 MIPS 指令集,來看看機器碼是如何生成的。(intel 指令集有 2000 條左右,而且是變長,不太好理解)

3.1 MIPS 指令集

MIPS 是一組由 MIPS 技術公司在 80 年代中期設計出來的 CPU 指令集。就在最近,MIPS 公司把整個指令集和芯片架構都完全開源了。MIPS 指令集管網:https://www.mips.com/mipsopen/

圖3:MIPS 指令集格式

MIPS 的指令是一個 32 位的整數,高 6 位叫操作碼(Opcode),也就是代表這條指令具體是一條什么樣的指令,剩下的 26 位有三種格式,分別是 R、I 和 J。

  • R 指令:一般用來做算術和邏輯操作,里面有讀取和寫入數據的寄存器的地址。如果是邏輯位移操作,后面還有位移操作的位移量,而最后的功能碼,則是在前面的操作碼不夠的時候,擴展操作碼表示對應的具體指令的。
  • I 指令:則通常是用在數據傳輸、條件分支,以及在運算的時候使用的並非變量還是常數的時候。這個時候,沒有了位移量和操作碼,也沒有了第三個寄存器,而是把這三部分直接合並成了一個地址值或者一個常數。
  • J 指令:跳轉指令,高 6 位之外的 26 位都是一個跳轉后的地址。

3.2 示例:add 指令

add $t0,$s2,$s1

add 對應的 MIPS 指令里 opcode 是 0,rs 代表第一個寄存器 s1 的地址是 17,rt 代表第二個寄存器 s2 的地址是 18,rd 代表目標的臨時寄存器 t0 的地址,是 8。因為不是位移操作,所以位移量是 0。把這些數字拼在一起,就變成了一個 MIPS 的加法指令。

為了讀起來方便,我們一般把對應的二進制數,用 16 進制表示出來。在這里,也就是 0X02324020。這個數字也就是這條指令對應的機器碼。

圖4:add 指令表示方法
  000000 10001 10010 01000 00000 100000     // add MIPS指令格式
= 0000 0010 0011 0010 0100 0000 0010 0000   // add 二進制表示
= 0X02324020                                // add 十六進制表示

4. 總結延伸

這一講里,我們看到了一個 C 語言程序,是怎么被編譯成為匯編語言,乃至通過匯編器再翻譯成機器碼的。

除了 C 這樣的編譯型的語言之外,不管是 Python 這樣的解釋型語言,還是 Java 這樣使用虛擬機的語言,其實最終都是由不同形式的程序,把我們寫好的代碼,轉換成 CPU 能夠理解的機器碼來執行的。只是解釋型語言,是通過解釋器在程序運行的時候逐句翻譯,而 Java 這樣使用虛擬機的語言,則是由虛擬機對編譯出來的中間代碼進行解釋,或者即時編譯成為機器碼來最終執行。

然而,單單理解一條指令是怎么變成機器碼的肯定是不夠的。接下來的兩個小節,我會深入講解,包含條件、循環、函數、遞歸這些語句的完整程序,是怎么在 CPU 里面執行的。

推薦閱讀:

  1. 《計算機組成與設計:軟 / 硬件接口》第 5 版的 2.17 小節:Intel CPU 的指令集

每天用心記錄一點點。內容也許不重要,但習慣很重要!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM