---
# System prepended metadata

title: 'Assembly Language / Ch03 - Data Transfers, Addressing, and Arithmetic'
tags: [Assembly Language]

---

# 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（呱呱）***