## 2.5 操作數選擇
一條指令可以有零或多個操作數-指令操作的數據。零操作數的一個例子是NOP(no operation)。操作數可以在下面的位置:
+ 位于指令本身(立即數)
+ 位于寄存器(EAX, EBX, ECX, EDX, ESI, EDI, ESP, 或者EBP,如果是32位操作數;AX, BX, CX, DX, SI, DI, SP, 或者BP,如果是16位操作數;AH, AL, BH, BL, CH, CL, DH, 或者DL,如果是8位操作數;段寄存器;位操作的EFLAGS寄存器)
+ 位于存儲器
+ 位于I/O端口
立即數和寄存器操作可以比存儲器操作數有更快的訪問速度,因為存儲器操作數必須從存儲器中取出來。寄存器操作數可以從CPU中獲得。立即數同樣從CPU獲得,因為它們被作為指令的一部分而被預取。
對于那些帶有操作數的指令來說,有些隱式的聲明操作數;有些顯式的聲明操作數;其他的使用隱式和顯式兩種方式的組合來聲明操作數;例如:
隱式操作數:AAM
根據定義,AAM(ASCII adjust for multiplication)操作AX寄存器。
顯示操作數:XCHG EAX, EBX
要交換的操作數被編碼在指令中操作碼后面的位置。
隱式和顯式操作數:PUSH COUNTER
存儲器變量COUNTER(顯式操作數)被拷貝到堆棧(隱式操作數)的頂部。
注意:大部分指令都有隱式操作數。例如,所有的算術指令更新EFLAGS寄存器。
80386指令可以顯式的引用1到2個操作數。2個操作數的指令,如MOV, ADD, XOR等,通常用結果覆蓋其中一個參與操作數。因此可以區分處源操作數(不受操作影響的一個)和目的操作數(被結果覆蓋的一個)。
對于多數指令,兩個顯式指定的操作數的一個-或者是源,或者是目的操作數-可以位于寄存器或者存儲器。另一個操作數必須是寄存器或者是立即數作為源操作數。因此,顯式2個操作數指令允許操作數有如下幾種:
+ 寄存器到寄存器
+ 寄存器到存儲器
+ 存儲器到寄存器
+ 立即數到寄存器
+ 立即數到存儲器
然而,一些字符串指令和堆棧操作指令從存儲器到存儲器傳輸數據。有些字符串指令隱式的聲明操作數,并且都位于存儲器。入棧和出棧操作允許在存儲器和基于存儲器的堆棧之間傳輸數據。
### 2.5.1 立即數
一些指令使用指令自身的一部分數據作為一個(有時是兩個)操作數。這樣的操作數被稱作是立即數。操作數可以是32位,16位,或者8位長。例如:
```
SHR PATTERN, 2
```
指令的一個字節含有數值2,變量PATTERN的移位數。
```
TEST PATTERN, 0FFFF00FFH
```
指令中的雙字包含要測試變量PATTERN的掩碼。
### 2.5.2 寄存器操作數
操作數可以被放置在下面32位通用寄存器之一(EAX, EBX, ECX, EDX, ESI, EDI, ESP, 或者EBP),16位通用寄存器之一(AX, BX, CX, DX, SI, DI, SP, 或者BP),8位通用寄存器之一(AH, BH, CH, DH, AL, BL, CL, 或者DL)。
80386有引用段寄存器的指令(CS, DS, ES, SS, FS, GS)。只有在系統設計人員選擇了段模式的時候,應用程序才能使用這些指令。
80386有些指令用來引用標志寄存器。標志位可以存儲在堆棧中,并從堆棧中恢復。一些指令會直接改變在EFLAGS中通常會被修改的標志位。其他標志位很少被修改,它們可以通過堆棧中的標志位鏡像而被間接的改變。
### 2.5.3 存儲器操作數
尋址存儲器操作數的數據操作指令必須聲明(直接或間接的)包含操作數的段以及操作數在段內的偏移量。然而,為了指令編碼的速度和緊湊,段選擇符被存儲在高速段寄存器內。因此,如果要尋址一個存儲器操作數,數據操作指令只需要聲明要求的段寄存器和偏移量。
尋址存儲器操作數的80386數據操作指令使用下面方法之一來聲明操作數在段內的偏移量:
1.大多數訪問存儲器的數據操作指令包含一個字節,顯式的指明操作數的尋址方式。一字節,被認為是modR/M字節,跟在操作碼后面,指明操作數位于寄存器還是存儲器中。如果操作數在存儲器中,地址由一個段寄存器和下面的任意值計算出來:基址寄存器,索引寄存器,縮放因子,移位。當使用索引寄存器時,modR/M字節跟在標識索引寄存器和縮放因子的字節之后,這種尋址方式具有最高的自由度。
2.一些數據操作指令隱式的使用特殊尋址方式:
+ 對于一些隱式使用EAX寄存器的短型MOV,操作數的偏移量被編碼為雙字放入指令中。沒有使用基址寄存器,索引寄存器以及縮放因子。
+ 字符串操作隱式的使用DS:ESI, (MOVS, CMPS, OUTS, LODS, SCAS) 或者ES:EDI (MOVS, CMPS, INS, STOS)來尋址。
+ 堆棧操作隱式的通過SS:ESP寄存器來尋址;例如,PUSH, POP, PUSHA, POPA, POPAD, PUSHF, PUSHFD, POPF, POPFD, CALL, RET, IRET, IRETD, 異常以及中斷。
2.5.3.1 段選擇
數據操作指令不需要顯式聲明使用哪個段寄存器。對于所有這些指令,段寄存器的聲明是可選的。對于所有的存儲器訪問,如果沒有顯式的聲明一個段,處理器根據表2-1的規則來自動選擇一個段寄存器。(如果系統設計人員已經選擇了平坦模式,那么段寄存器和處理器選擇它們的規則對于應用程序來說不是很明顯)。
存儲器引用的類型和操作數駐留的段之間有著緊密的聯系。通常,引用存儲器暗示著當前段為數據段(即,暗示著段選擇符在DS中)。然而,ESP和EBP用來訪問堆棧中的數據項;因此,當ESP和EBP作為基址寄存器使用時,當前段為堆棧段(即,SS含有選擇符)。
特殊的指令前綴要素可以覆蓋默認的段選擇。段重載前綴允許顯式的段選取。80386對于每個段寄存器都有一個段重載前綴。只有在下面的特殊情況下,隱式的段選擇才不會被重載:
+ 在字符串指令中用于目的字符串的ES。
+ 堆棧指令中SS的使用。
+ 取指令時CS的使用。
表2-1. 缺省段寄存器選擇規則
| 需要存儲器引用 | 使用的段寄存器 | 隱式段選擇規則 |
| --- | --- | --- |
| 指令 | Code(CS) | 自動指令預取 |
| 堆棧 | Stack(SS) | 所有堆棧的入棧和出棧。任何使用ESP或者EBP作為基址寄存器的存儲器引用。 |
| 本地數據 | Data(DS) | 除了堆棧和目的字符串的所有數據引用。 |
| 目的字符串 | Extra(ES) | 字符串指令的目的操作數。 |
2.5.3.2 有效地址計算
modR/M字節為尋址方法提供了最大的自由度,需要modR/M作為第二個字節的指令在80386指令集中非常常見。對于由modR/M定義的存儲器操作來說,段內偏移通過對下面三部分求和計算出來:
+ 指令中的移位。
+ 基址寄存器。
+ 索引寄存器。索引寄存器可能會自動與縮放因子2,4,或者8相乘。
上面生成的結果稱為有效地址。構成有效地址的每個成員可以時正值,也可以是負值。如果所有成員的和超過了232,有效地址會被截短為32位值。圖2-10展示了modR/M尋址的所有可能。
移位,由于是編碼在指令中,所以作固定運算很有用;例如:
+ 簡單縮放操作數的位置。
+ 靜態分配數組的開始。
+ 記錄中一項的偏移量。
基址和索引具有類似的功能。都使用相同的通用寄存器集合。都可以用來計算地址中需要動態確定的部分;例如:
+ 進程參數的位置以及堆棧中的局部變量。
+ 在幾個相同記錄類型的記錄或記錄數組中,其中一個的開始。
+ 一位數組或多維數組的開始。
+ 動態分配數組的開始。
當通用寄存器用于基址或索引時,它們有下面的不同:
+ ESP不能用作索引寄存器。
+ 當ESP或者EBP用于基址寄存器時,缺省段由SS指定。所有其他情況,使用DS作為段選擇器。
縮放因子允許數組項是2,4,或8字節寬時用索引高效的訪問數組。索引寄存器的移位在尋址時由處理器完成,不會損失性能。也避免了單獨的移位或乘法指令。
基址,索引,和移位這三個部件可以任意組合;任何一個上面的部件可以是空的。縮放因子只有在使用了索引因子時才能用。每種可能的組合對于由高級語言程序員和匯編程序員使用的數據結構非常有用。下面時一些尋址部件不同組合后的可能使用:
移位
單獨的移位只是操作數的偏移量。該部件用于直接尋址靜態分配的縮放操作數。可以使用8位,16位或32位移位。
基址
操作數的偏移量由通用寄存器的一個間接聲明,作為“基”變量。
基址 + 移位
寄存器和移位的組合可以用于兩種不同的目的:
1.對中元素大小不是2,4,或8字節的靜態數組進行索引。移位編碼為相對于數組開始的偏移量。寄存器內保存計算結果,決定一項指定的元素在數組內的偏移量。
2.訪問記錄中的一項。移位定位記錄中的數據項。寄存器選擇出現的記錄中的一個,因此為這種常見的功能提供了一種緊湊的編碼。
這種組合一個重要的特殊情形就是訪問在堆棧中的進程活動記錄的參數。這種情況下,EBP是作為基址寄存器的最好選擇,因為當EBP被用作基址寄存器時,存儲器自動使用堆棧段寄存器(SS)來定位操作數,因此為這種常見的功能提供了一種緊湊的編碼。
(索引 + 縮放) + 移位
當靜態數組中元素大小是2,4,或者8時,這種組合提供了有效的索引。移位定位在數組的開始,索引寄存器保存要求的數組元素的下標,處理器自動應用縮放因子將下標轉換為索引。
基址 + 索引 + 移位
兩個寄存器加在一起支持二維數組(移位決定數組的開始)或者記錄數組的一個實例(移位指示記錄中的一項)。
基址 + (索引 * 縮放) + 移位
當數組中元素是2,4,或者8字節寬時,這種組合提供了一種二維數組的高效索引。

- 第一章 80386介紹
- 1.1 該手冊的組織結構
- 1.2 其他文獻
- 第二章 編程基本模型
- 2.1 存儲器組織和段
- 2.2 數據類型
- 2.3 寄存器
- 2.4 指令格式
- 2.5 操作數選擇
- 2.6 中斷和異常
- 第4章 系統寄存器
- 4.1 系統寄存器 (System Registers)
- 4.2 系統指令 (System Instructions)
- 第五章 內存管理
- 5.1 分段地址轉換(Segment Translation)
- 5.2 分頁地址轉換(Page Translation)
- 5.3 混合分段和分頁地址轉換(Combining Segment and Page Translation)
- 第六章 內存管理
- 6.1 為什么要保護(Why Protection?)
- 6.2 80386保護機制概述(Overview of 80386 Protection Mechnaisms)
- 6.3 段級保護(Segment-Level Protection)
- 6.4 頁級保護(Page-Level Protection)
- 6.5 混合分頁和分段保護(Combining Page and Segment Protection)
- 第7章 多任務(Multitasking)
- 8.1 I/O 尋址(I/O Addressing)
- 7.1 任務狀態段(Task State Segment)
- 7.3 任務寄存器(Task Register)
- 7.4 任務門描述符(Task Gate Descriptor)
- 7.5 任務切換(Task Switching)
- 7.6 任務鏈(Task Linking)
- 7.7 任務尋址空間(Task Address Space)
- 第8章 輸入 輸出
- 8.2 I/O 指令(I/O Instructions)
- 8.3 保護和I/O(Protection and I/O)
- 第9章 異常和中斷(Exceptions and Interrupts)
- 9.1 識別中斷(Identifying Interrupts)
- 9.2 允許和禁止中斷(Enabling and Disabling Interrupts)
- 9.3 同時發生的中斷和異常的優先級(Priority Among Simultaneous Interrupts and Exceptions)
- 9.4 中斷描述符表(Interrupt Descriptor Table)
- 9.5 IDT 描述符(IDT Descriptors)
- 9.6 中斷任務和中斷子程序(Interrupt Tasks and Interrupt Procedures)
- 9.7 出錯碼(Error Code)
- 9.8 異常條件(Exception Conditions)
- 9.9 異常總結(Exception Summary)
- 9.10 出錯碼總結(Error Code Summary)
- 第10章 初始化(Initialization)
- 10.1 復位后處理器狀態(Processor State After Reset)
- 10.2 實模式初始化(Software Initialization for Real-Address Mode)
- 10.3 切換到保護模式(Switching to Protected Mode)
- 10.4 保護模式初始化(Software Initialization for Protected Mode)
- 10.5 初始化示例
- 10.6 TLB測試
- 第十四章 80386實地址模式
- 14.1 物理地址構成
- 14.2 寄存器和指令
- 14.3 中斷和異常處理
- 14.4 進入和離開實地址模式
- 14.6 實地址模式異常
- 14.7 與8086的不同
- 14.8 與80286實地址模式的不同