博客 / 詳情

返回

用匯編語言編寫計算兩整數之和的程序(上)

本文節選自《計算機是怎樣跑起來的(第2版)》第 3 章“體驗彙編語言”的草稿。在翻譯本章時,我們發現原書所使用的軟件僅提供日文界面,並且介紹的是主要用於日本計算機相關考試的 CASLⅡ 彙編語言,其通用性相對較低。為了讓內容更廣泛適用,並便於讀者實踐操作,與作者及編輯老師商議後,決定採用更為通用的 NASM 彙編語言 重新編寫本章,以提升學習體驗。

如何編程計算兩個整數的和呢?恐怕無論使用哪一種主流編程語言,甚至是從未接觸過的新語言,解決這個問題都不費吹灰之力吧,除了 num1 + num2 還能有其他寫法嗎?

不過,為了加深對計算機的理解,我們特意自討苦吃,看看如何用 NASM 這種彙編語言編寫程序,並藉助一款名為 SASM 的軟件剖析該程序的運行情況。

彙編語言屬於低級語言

編程語言大致可以分為低級語言高級語言兩大類。低級語言包括機器語言彙編語言。使用低級語言書寫的程序能夠直接操作計算機硬件。

在機器語言中,任何指令和數據都要用二進制數表示。由於使用機器語言編程很不方便,人們發明了彙編語言。彙編語言使用英語單詞的縮寫來表示指令,使得程序員無須再記憶指令對應的二進制數字。

不過,用匯編語言編寫的程序需要先轉換成機器語言的程序才能由 CPU 解釋執行。彙編語言的指令和機器語言的指令是一一對應的

必備的硬件知識

使用匯編語言編程時必須瞭解一些硬件知識。對於計算兩整數之和這個程序,我們只需要瞭解一些有關 CPU 的寄存器和內存存儲單元的知識就足夠了。雖然這個程序最後會在屏幕上輸出計算結果,但這是通過調用預設的指令(稱作)實現的,並沒有直接操作 I/O。

寄存器

CPU 內部有多個寄存器,每個寄存器都有一個唯一的名字。例如,在 Intel CPU 中,寄存器的名字是 eax、ecx、edx、ebx 等。我們可以將這些寄存器視作變量,使用它們來執行運算。

eip 寄存器是一個很關鍵的寄存器,其中存儲的是正在執行的指令的地址(存儲着指令的內存單元的地址)。每執行完一條指令,eip 寄存器的值都會自動更新為下一條指令的地址。

內存

內存中的每個存儲單元都有一個唯一的地址,存儲單元之間通過地址加以區分。內存地址多用十六進制數表示。

彙編語言的語法只有一條

用匯編語言(這裏使用的是 NASM)編寫的計算兩整數之和(這裏是計算 1+2)的程序代碼如下所示。

彙編語言其實是 NASM、MASM、FASM 等一類計算機語言的統稱,本文選用了語法上較為簡單的 NASM 彙編語言。
%include "io.inc"

section .data
    A   dd 1
    B   dd 2
    ANS dd 0

section .text
global main
main:
    mov eax, [A]
    add eax, [B]
    mov [ANS], eax
    PRINT_DEC 4, ANS
    xor eax, eax
    ret

彙編語言的代碼乍看之下非常晦澀,可實際上並非如此。因為彙編語言的語法基本上只有一條,即 指令 指令的對象。指令既可以沒有對象,也可以帶一個或兩個對象。兩個指令的對象之間要用逗號分隔。指令也稱作操作碼(opcode,operation code),即表示操作的代碼,指令的對象也稱作操作數(operand)。

例如,mov eax, [A] 這一行代碼中的 mov 是指令,eax 是該指令的第一個對象,[A] 是第二個對象。又如最後一行代碼,ret 這個指令就沒有對象。

在彙編語言中,操作數通常是 CPU 中的寄存器或內存中的存儲單元,這是因為彙編語言正是用於描述以下操作的編程語言:

  • 對存儲在 CPU 的寄存器中的數據進行計算
  • 將存儲在內存的存儲單元中的數據讀取到寄存器中
  • 將計算結果存儲在內存的存儲單元裏
  • 將主機與外部設備之間輸入/輸出的數據存儲在 I/O 的存儲單元裏

一行彙編語言的代碼(語句)除了指令本身(操作碼)和指令的對象(操作數),有時還包括標籤(label)和註釋(comment)。

標籤是程序員為指令或數據賦予的名稱,主要用於説明指令或數據的含義。在上面的代碼中,main 標籤表示程序執行的起點,而 ABANS 也是標籤,分別表示第一個加數、第二個加數和計算結果(answer)。稍後我們將會看到,標籤本質上就是內存中存儲空間的地址。為了避免使用由雜亂無章的數字組成的內存地址,程序員往往使用標籤指代存儲空間。

註釋是程序員為代碼添加的文字説明。在本文使用的名為 NASM 的彙編語言中,註釋要寫在分號 ; 之後。

逐行分析“計算 1+2”的代碼

下面就來逐行分析代碼清單中的代碼。

%include "io.inc"

section .data
    A   dd 1
    B   dd 2
    ANS dd 0

section .text
global main
main:
    mov eax, [A]
    add eax, [B]
    mov [ANS], eax
    PRINT_DEC 4, ANS
    xor eax, eax
    ret

可以看到,兩個空行將這段代碼分成了三部分。第一部分只有 1 行,%include "io.inc" 表示包含一個名為 io.inc 的文件,這樣我們就可以調用其中的預設指令 PRINT_DEC,向屏幕輸出計算結果了。

彙編語言的代碼通常會分為幾個段(section),最常見的段是代碼段(.text section)數據段(.data section),前者包含了程序中的指令,後者包含的是數據。

section .data 表示數據段的起點,其中包含三條“指令”,各條指令的作用如下:

  • A dd 1:把整數 1 存儲到由 4 個連續的存儲單元(4 字節)構成的存儲空間中,併為這塊空間貼上一個叫作 A 的標籤,表示這是第一個加數。相當於高級語言中的 A = 1
  • B dd 2:把整數 2 存儲到由 4 個連續的存儲單元(4 字節)構成的存儲空間中,併為這塊空間貼上一個叫作 B 的標籤,表示這是第二個加數。相當於高級語言中的 B = 2
  • ANS dd 0:把整數 0(初始值)存儲到由 4 個連續的存儲單元(4 字節)構成的存儲空間中,併為這塊空間貼上一個叫作 ANS 的標籤,表示這是計算結果。相當於高級語言中的 ANS = 0

至此,數據段就結束了,空行之後的 section .text 表示接下來要進入代碼段了。

代碼段中的第一條指令是 global main,其中的 main 是一個標籤,下一行的 main: 正是這個叫作 main 的標籤本身,這是一個特殊的標籤,表示程序執行的起點。也就是説,CPU 將從貼有 main 標籤的指令,即下一行的 mov eax, [A] 開始解釋執行程序。雖然 main 和數據段中的 ABANS 都是標籤,但因為 main 單獨佔了一行,所以習慣上要在結尾處加上冒號,以明確表示這是一個標籤,而不是一條叫作 main 的指令。

前面的代碼都是在為“計算 1+2”做準備,從 mov eax, [A] 這一行開始,才真正開始進入計算環節。

    mov eax, [A]
    add eax, [B]
    mov [ANS], eax
    PRINT_DEC 4, ANS

mov(move 的縮寫)指令會將存儲在 A 標籤中的數據複製到 CPU 的 eax 寄存器中。這裏的 [] 表示“存儲在標籤中的數據”,若不加 [],這條指令就成了“將 A 標籤本身(本質上是內存地址)複製到 eax 中”,這就不是我們的意圖了。[] 有點像高級語言中的解引用(如 C 語言中的 eax = *A)。

下一條指令是 add eax, [B],這裏的 add 顧名思義,表示執行加法運算,參與加法運算的兩個操作數分別是存儲在 eax 寄存器中的數據和存儲在 B 標籤中的數據。該指令會把加法運算的結果存回到 eax 寄存器中,類似高級語言中的 eax = eax + *B

接下來又是 mov 指令,這條指令會將存儲在 eax 寄存器中的計算結果存儲到(複製到)ANS 標籤中(貼有 ANS 標籤的存儲單元中),類似高級語言中的 *ANS = eax

“把 A+B 的結果存儲到 ANS 中”,如此簡單的運算看似一步就能完成,可到了彙編語言中竟然需要分三步才能實現。為了輸出“好不容易”才計算出的結果,程序最後調用了預設的指令 PRINT_DEC 來輸出 ANS 的值。由於 ANS 這塊存儲空間佔 4 字節,所以 PRINT_DEC 的第一個操作數是 4

安裝彙編語言編程工具 SASM

瞭解了每行代碼的含義後,我們再來使用 SASM 驗證一下這個程序的行為,看看程序輸出的結果對不對。SASM 是一款免費的彙編語言編程工具,自帶調試功能,非常適合初學者用來學習彙編語言。SASM 可從以下頁面獲取。

http://dman95.github.io/SASM/english.html

SASM 與主流 IDE 的使用方法非常類似,代碼編寫好以後,點擊工具欄上的“構建並運行”(圖標是綠色的三角形)按鈕。如果代碼中沒有錯誤,就會在窗口底部的窗格中看到一行綠色的文字 程序正常完成,同時會在右側的“輸出”窗格中看到正確的計算結果 3,如下圖所示。


至此,我們終於得到了一個 NASM 彙編語言版本的“計算兩整數之和”,嚴格説來這段程序只能計算 1+2,而不是任意的兩整數之和。

彙編語言的程序需要先轉換成機器語言的程序才能由 CPU 解釋執行,而且彙編語言的指令和機器語言的指令是一一對應的。那“計算 1+2”這段代碼對應着怎樣的機器語言的代碼呢?

另外,在高級語言中,計算兩整數之和可以只用兩個變量 a += b,但在彙編語言中,為什麼不能寫成 add [A], [B] 呢?

接下來,我們將利用 SASM 的調試功能探索這些問題。

user avatar nixidezuqiu 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.