# Assembly Language / Ch03 - Data Transfers, Addressing, and Arithmetic
## 1. Data Transfer Instructions 資料傳輸指令
數據傳輸是一個基本而重要的操作,不過與高階語言不同,組合語言對變量和賦值語句的類型檢查較少,這給了開發人員更大的自由度,但同時也要求開發人員更加謹慎。
### 1.1 Operand Types
在 x86 組合語言中,操作數是用來指出指令執行的數據來源,它們主要分為三種類型:立即數、暫存器和內存。以下是每種操作數的詳細解說:
#### 1.1.1 立即數(Immediate)
立即數是指令中指定的常數,並且直接嵌入在指令中使用。因為立即數是硬編碼在指令內,因此它的值在執行期間無法改變。
- **特點**:
- 立即數的位數通常為 8 位、16 位或 32 位。
- 它們是唯讀的,不能被覆寫。
- **範例**:`MOV AX, 5`
- 上面的指令將整數 5(立即數)移到 `AX` 暫存器中。
- **使用情境**:
- 立即數常用於設定初始值、加法、減法等不需變動的操作。
- 因其不可變性,用立即數可確保特定值在程式運行期間不會變更。
#### 1.1.2 暫存器(Register)
暫存器是 CPU 中的高速存儲器,用來暫時存放數據。它們可以在運行時讀寫,並用於快速執行操作。每個暫存器具有不同的大小和用途,例如通用暫存器、段暫存器等。
- **特點**:
- CPU 提供了多種暫存器,例如 `AX`, `BX`, `CX`, `DX`(16 位暫存器)和 `EAX`, `EBX`, `ECX`, `EDX`(32 位暫存器)。
- 暫存器的訪問速度遠快於內存,這使得操作數儲存在暫存器中時的執行速度更快。
- **範例**:`MOV BX, AX`
- 上面的指令將 `AX` 暫存器中的數據移動到 `BX` 暫存器。
- **使用情境**:
- 暫存器適合用於頻繁訪問的數據,例如計數器(`CX` 通常用作迴圈計數器)。
- 也用於數學計算中,因為操作數在暫存器中的運算速度最快。
#### 1.1.3 內存(Memory)
內存是用來儲存數據的物理儲存區域。操作數可以直接從內存讀取或寫入,但相比暫存器,內存訪問速度較慢。使用內存操作數時,指令中會包含內存地址,或使用暫存器來指向內存地址。
- **特點**:
- 內存操作數可通過多種尋址模式存取,如直接、間接、偏移等方式。
- 內存中的數據可以被多次訪問和修改。
- **範例**:
- `MOV AX, [SI]`:將 `SI` 暫存器指向的內存位置的數據移到 `AX` 暫存器。
- `MOV [BX], 3`:將立即數 3 移到 `BX` 指向的內存位置。
- ```assembly=
.data
var1 BYTE 10h
.code
mov al, var1 ; AL = 10h
mov al, [var1] ; AL = 10h,與上面指令效果相同
```
- **使用情境**:
- 內存適合儲存較大或需長期儲存的數據。
- 通常用於陣列、字串和大範圍的資料結構。
#### 1.1.4 彙整
| 操作數 | 描述 | 典型範例 | 使用情境 |
|--------|--------------------|--------------------------|----------------------|
| 立即數 | 指令中直接編碼的數值 | `MOV AX, 10` | 設定固定值、加減操作等 |
| 暫存器 | CPU 中的高速儲存單元 | `MOV BX, AX` | 頻繁訪問數據、計算 |
| 內存 | 外部儲存的數據地址 | `MOV AX, [SI]` | 儲存較大或需持久的數據 |
### 1.2 MOV 指令
MOV 指令是最基本的數據傳輸指令,它將數據從源操作數移動到目標操作數。其語法為:
```assembly
MOV destination, source
```
使用 MOV 指令時,需要注意以下幾點:
- 不允許兩個操作數都是內存操作數
- CS、EIP 和 IP 不能作為目標操作數
- 不允許將立即數直接移動到段暫存器
- 兩個操作數必須是相同的大小
例如:
```assembly=
.data
count BYTE 100
wVal WORD 2
.code
mov bl, count ; 有效
mov ax, wVal ; 有效
mov count, al ; 有效
mov al, wVal ; 錯誤 - 大小不匹配
mov ax, count ; 錯誤 - 大小不匹配
mov eax, count ; 錯誤 - 大小不匹配
```
### 1.3 MOVZX 和 MOVSX 指令
當需要將較小的值複製到較大的目標操作數時,可以使用 MOVZX(zero extension,零擴展)和 MOVSX(sign extension,符號擴展)指令。
MOVZX 指令會用零填充目標操作數的高位部分:
```assembly
mov bl, 10001111b
movzx ax, bl ; AX = 0000000010001111b
```
MOVSX 指令會用源操作數的符號位填充目標操作數的高位部分:
```assembly
mov bl, 10001111b
movsx ax, bl ; AX = 1111111110001111b
```
### 1.4 XCHG 指令
XCHG 指令用於交換兩個操作數的值。至少一個操作數必須是暫存器,**不允許使用立即數操作數**。
```assembly=
.data
var1 WORD 1000h
var2 WORD 2000h
.code
xchg ax, bx ; 交換 16 位暫存器
xchg ah, al ; 交換 8 位暫存器
xchg var1, bx ; 交換內存和暫存器
xchg eax, ebx ; 交換 32 位暫存器
xchg var1, var2 ; 錯誤:兩個內存操作數
```
### 1.5 直接偏移運算元
透過加入常數偏移值,可以快速存取特定位置的值,例如陣列的特定元素。
```assembly=
.data
arrayB BYTE 10h, 20h, 30h, 40h
.code
mov al, arrayB ; AL = 10h
mov al, arrayB+1 ; AL = 20h
```
## 2. Addition and Subtraction 加法和減法
### 2.1 INC 和 DEC 指令
INC(increment)和 DEC(decrement)指令分別用於將操作數加 1 或減 1。操作數可以是暫存器或內存。
```assembly=
.data
myWord WORD 1000h
myDword DWORD 10000000h
.code
inc myWord ; myWord = 1001h
dec myWord ; myWord = 1000h
inc myDword ; myDword = 10000001h
mov ax, 00FFh
inc ax ; AX = 0100h
mov ax, 00FFh
inc al ; AX = 0000h
```
### 2.2 ADD 和 SUB 指令
ADD 和 SUB 指令用於執行加法和減法操作。
```assembly=
.data
var1 DWORD 10000h
var2 DWORD 20000h
.code
mov eax, var1 ; EAX = 00010000h
add eax, var2 ; EAX = 00030000h
add ax, 0FFFFh ; EAX = 0003FFFFh
add eax, 1 ; EAX = 00040000h
sub ax, 1 ; EAX = 0004FFFFh
```
### 2.3 NEG 指令
NEG 指令用於求一個數的二補數,即反轉其符號。
```assembly=
.data
valB BYTE -1
valW WORD +32767
.code
mov al, valB ; AL = -1
neg al ; AL = +1
neg valW ; valW = -32767
```
## 3. 數據相關運算符和指令 Data-Related Operators and Directives
### 3.1 OFFSET 運算符
OFFSET 運算符返回一個標籤(變量)從其封閉段開始的距離(以字節為單位)。
```assembly=
.data
bVal BYTE ?
wVal WORD ?
dVal DWORD ?
dVal2 DWORD ?
.code
mov esi, OFFSET bVal ; ESI = 00404000 (假設)
mov esi, OFFSET wVal ; ESI = 00404001
mov esi, OFFSET dVal ; ESI = 00404003
mov esi, OFFSET dVal2 ; ESI = 00404007
```
### 3.2 PTR 運算符
PTR 運算符用於覆蓋變量的默認類型,提供了訪問變量部分內容的靈活性。
```assembly=
.data
myDouble DWORD 12345678h
.code
mov ax, WORD PTR myDouble ; AX = 5678h
mov WORD PTR myDouble, 4321h ; 保存 4321h
```
### 3.3 TYPE、LENGTHOF 和 SIZEOF 運算符
- TYPE 運算符返回數據聲明中單個元素的大小(以字節為單位)。
- LENGTHOF 運算符計算單個數據聲明中的**元素數量**。
- SIZEOF 運算符返回等於 **`LENGTHOF*TYPE`** 的值。
```assembly=
.data
byte1 BYTE 10, 20, 30
array1 WORD 30 DUP(?), 0, 0
; ___可以自己丟進VS試試看
array2 WORD 5 DUP(3 DUP(?))
array3 DWORD 1, 2, 3, 4
digitStr BYTE "12345678", 0
.code
mov eax, TYPE byte1 ; EAX = 1
mov eax, LENGTHOF array1 ; EAX = 32
mov eax, SIZEOF array1 ; EAX = 64
```
## 4. 間接尋址 Indirect Addressing
間接尋址是一種使用暫存器作為指標的方法,特別適用於數組處理。
### 4.1 間接操作數
間接操作數保存變量的地址,通常是數組或字符串。它可以被解引用(就像指標一樣)。
```assembly=
.data
val1 BYTE 10h, 20h, 30h
.code
mov esi, OFFSET val1
mov al, [esi] ; 解引用 ESI (AL = 10h)
inc esi
mov al, [esi] ; AL = 20h
inc esi
mov al, [esi] ; AL = 30h
```
### 4.2 索引操作數
索引操作數通過將常數添加到暫存器來生成有效地址。有兩種表示形式:`[label + reg]` 和 `label[reg]`。
```assembly=
.data
arrayW WORD 1000h, 2000h, 3000h
.code
mov esi, 0
mov ax, [arrayW + esi] ; AX = 1000h
mov ax, arrayW[esi] ; 替代格式
add esi, 2
add ax, [arrayW + esi] ; AX = 2000h
```
## 5. JMP and LOOP Instructions
### 5.1 JMP 指令
JMP 是一個無條件跳轉指令,通常跳轉到同一過程內的標籤。
```assembly=
top:
; 一些代碼
jmp top
```
### 5.2 LOOP 指令
LOOP 指令創建一個計數循環。它會將 ECX 減 1,如果 ECX 不為零,則跳轉到目標標籤。
```assembly=
mov ax, 0
mov ecx, 5
L1:
add ax, cx
loop L1
```
這個範例計算了 5 + 4 + 3 + 2 + 1 的和。
### 5.3 數組求和示例
以下代碼計算了一個 16 位整數數組的和:
```assembly=
.data
intarray WORD 100h, 200h, 300h, 400h
.code
mov edi, OFFSET intarray ; intarray 的地址
mov ecx, LENGTHOF intarray ; 循環計數器
mov ax, 0 ; 初始化累加器
L1:
add ax, [edi] ; 加上陣列中的首元素
add edi, TYPE intarray ; 指向下一個整數元素
loop L1 ; 重複直到 ECX = 0
```
### 5.4 範例 1 - Copying a String
以下代碼將一個字符串從 `source` 複製到 `target` :
```assembly=
.data
source BYTE "This is the source string", 0
target BYTE SIZEOF source DUP(0)
.code
mov esi, 0 ; 索引暫存器
mov ecx, SIZEOF source ; 循環計數器
L1:
mov al, source[esi] ; 從源獲取字符
mov target[esi], al ; 存儲到目標
inc esi ; 移動到下一個字符
loop L1 ; 重複整個字符串
```
### 5.5 範例 2 - Summing an Array
使用間接位址來遍歷陣列的每個元素並計算總和:
```assembly=
.data
intarray WORD 100h, 200h, 300h
.code
mov edi, OFFSET intarray
mov ecx, LENGTHOF intarray
mov ax, 0
L1:
add ax, [edi]
add edi, TYPE intarray
loop L1
```
## 6. OFFSET 運算元:深入解說
鑒於我第一次讀到OFFSET的時也沒看多懂加上一直被問,特此多為它開一個小節。
### 6.1 概述
`OFFSET` 是一個用來取得變數或標籤在記憶體中**位址偏移量**(offset)的運算元。它會返回指定變數從其所在區段(如 `.data` 或 `.bss`)開頭的距離(以位元組為單位)。這在組合語言中尤其重要,因為組合語言不像高階語言提供自動的位址管理。
### 6.2 應用情境
`OFFSET` 通常在以下情境中使用:
1. **指標運算**:需要取得變數的位址,並將其存入暫存器,方便後續指標操作(例如陣列操作或字串處理)。
2. **動態位址計算**:當變數的記憶體位址需要在運行時進行計算,並且需要進行加法或乘法運算來訪問特定位置。
3. **呼叫系統函數**:某些情況下,使用低階的 `CALL`、`JMP` 指令時需要指定函數或標籤的位址。
### 6.3 使用範例
#### 6.3.1 基本語法
```assembly=
.data
myVar BYTE 10h ; 定義一個 8 位元組變數
.code
mov esi, OFFSET myVar ; 將 myVar 的位址存入 ESI
```
在此範例中,`OFFSET myVar` 會計算出 `myVar` 的記憶體位址,這個位址是相對於 `.data` 區段起點的偏移量,並存入 ESI 暫存器。因此,`ESI` 會保存 `myVar` 的位址,而不是其值 `10h`。
#### 6.3.2 陣列與 OFFSET 的結合
`OFFSET` 在陣列操作中尤為實用,因為可以取得陣列起始位址,並透過加法來存取陣列中的元素:
```assembly
.data
arrayB BYTE 10h, 20h, 30h, 40h ; 定義一個字節陣列
.code
mov esi, OFFSET arrayB ; 將 arrayB 的位址存入 ESI
mov al, [esi] ; AL = 10h,存取第一個元素
add esi, 1 ; 將 ESI 增加 1,指向下一個元素
mov al, [esi] ; AL = 20h,存取第二個元素
```
在這個例子中,`OFFSET arrayB` 取得 `arrayB` 的起始位址並存入 `ESI`,透過增加 `ESI` 的值來訪問後續元素。這種操作非常靈活,特別適合用於遍歷陣列。
#### 6.3.3 OFFSET 結合 PTR 運算元
在組合語言中,由於不同變數的位元大小不同(如 BYTE、WORD、DWORD),我們常需要 `PTR` 運算元來明確指示所指向的資料大小。此時可以將 `OFFSET` 與 `PTR` 結合使用:
```assembly=
.data
myDouble DWORD 12345678h ; 定義一個雙字變數
.code
mov esi, OFFSET myDouble ; 取得 myDouble 的位址
mov ax, WORD PTR [esi] ; AX = 5678h,存取低 16 位元
```
在此例中,`WORD PTR [esi]` 用來存取 `myDouble` 的低 16 位元(因為 DWORD 為 32 位元資料)。`OFFSET` 提供位址,而 `PTR` 指明資料大小,讓暫存器正確載入位元數。
## 6.4 實際應用:動態存取陣列元素
透過 `OFFSET` 結合索引運算,可以根據需要靈活訪問不同位址中的資料。以下是一個計算 16 位元陣列元素總和的程式:
```assembly=
.data
intArray WORD 100h, 200h, 300h, 400h ; 定義一個 WORD 陣列
.code
mov edi, OFFSET intArray ; 將 intArray 的起始位址存入 EDI
mov ecx, LENGTHOF intArray ; 將陣列長度存入 ECX
mov ax, 0 ; 將 AX 清空以作為累加器
L1:
add ax, [edi] ; 將當前陣列元素加到 AX
add edi, TYPE intArray ; 更新 EDI 指向下一個元素位址
loop L1 ; 重複直到 ECX = 0
```
在這段程式碼中:
1. `OFFSET intArray` 取得 `intArray` 陣列的起始位址。
2. `TYPE intArray` 指定位移量(每次移動 2 位元組,因為 WORD 大小為 2 字節)。
3. `LENGTHOF intArray` 計算陣列元素數量,用於迴圈控制。
這種用法讓 `OFFSET` 和 `TYPE` 協同工作,實現對陣列的高效遍歷和處理。
### 6.5 OFFSET 的使用限制
在某些模式下,如實地址模式(Real-Address Mode),`OFFSET` 所能返回的位址偏移量受到 16 位元大小的限制。然而,在受保護模式(Protected Mode)下,`OFFSET` 返回 32 位元的偏移量,提供更多的位址範圍,適合更大的記憶體管理需求。
---
*註: 部分內容截自於網路,此筆記非完全原創。*
***Latest Updated On:2024.10.28,
published with the of LICENSE of WTFPL
Author:Qaron(呱呱)***