# 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(呱呱)***