x86汇编语言数据类型和整数运算
一、 数据在内存中的表示(数据类型)
在汇编层面,“数据类型”主要指数据在内存中的存储尺寸和解释方式。CPU指令根据尺寸决定操作多少内存。
| 类型 | 大小 (比特/字节) | 表示范围(无符号) | 表示范围(有符号) | 常见用途 |
|---|---|---|---|---|
| BYTE | 8 bits / 1 byte | 0 到 255 (0 到 FFh) | -128 到 +127 (80h ~ 7Fh) | ASCII字符,小整数 |
| WORD | 16 bits / 2 bytes | 0 到 65,535 (0 到 FFFFh) | -32,768 到 +32,767 | 短整数,旧式16位数据 |
| DWORD | 32 bits / 4 bytes | 0 到 4,294,967,295 | -2,147,483,648 到 +2,147,483,647 | 整数,内存地址(32位模式) |
| QWORD | 64 bits / 8 bytes | 0 到 \(1.84 \times 10^{19}\) | \(-9.22 \times 10^{18}\) 到 \(+9.22 \times 10^{18}\) | 64位整数,大型偏移 |
关键概念:
- 字节序:x86架构采用小端序。例如,双字
12345678h在内存中的存储顺序(从低地址到高地址)为:78h 56h 34h 12h。 - 有符号与无符号:内存中的二进制模式相同,解释权在指令。
0xFF作为无符号BYTE是 255,作为有符号BYTE是 -1。
定义数据示例(MASM):
.data
bVal BYTE 255 ; 无符号字节,FFh
sVal SBYTE -1 ; 有符号字节,也是FFh
wVal WORD 4660h ; 字,内存中为 60h 46h
dVal DWORD 12345678h; 双字
array BYTE 1,2,3,4 ; 字节数组
szMsg BYTE 'Hello',0; 以0结尾的字符串
二、 核心整数运算指令与标志位
运算的核心是寄存器和标志寄存器(EFLAGS)。指令会更新标志,后续条件跳转指令(如 JZ, JC)依赖这些标志。
1. 数据传送指令
| 指令 | 格式 | 作用 | 对标志位影响 |
|---|---|---|---|
| MOV | MOV dest, src |
将源数据复制到目标 | 无 |
| LEA | LEA reg, mem |
将内存操作数的有效地址加载到寄存器,不访问内存本身 | 无 |
| XCHG | XCHG op1, op2 |
交换两个操作数的值 | 无 |
MOV EAX, dVal ; 将内存dVal处的双字复制到EAX
LEA ESI, array ; 将array的地址存入ESI,相当于 MOV ESI, OFFSET array
XCHG EAX, EBX ; 交换EAX和EBX的值
2. 算术运算指令
下表总结了关键指令及其对标志位的影响(*表示直接影响):
| 指令 | 格式 | 作用 | OF(溢出) | SF(符号) | ZF(零) | CF(进位) |
|---|---|---|---|---|---|---|
| ADD | ADD dest, src |
加法:dest = dest + src |
* | * | * | * |
| SUB | SUB dest, src |
减法:dest = dest - src |
* | * | * | * |
| INC | INC op |
自增:op = op + 1 |
* | * | * | 无 |
| DEC | DEC op |
自减:op = op - 1 |
* | * | * | 无 |
| NEG | NEG op |
求补(取负):op = -op(即0-op) |
* | * | * | * |
| MUL | MUL reg/mem |
无符号乘法:将EAX与操作数乘,结果存EDX:EAX |
* | ? | ? | * |
| IMUL | IMUL dest, src1[, src2] |
有符号乘法:灵活的单/双/三操作数形式 | * | ? | ? | * |
| DIV | DIV reg/mem |
无符号除法:EDX:EAX ÷ op,商EAX,余EDX |
无定义 | 无定义 | 无定义 | 无定义 |
| IDIV | IDIV reg/mem |
有符号除法:EDX:EAX ÷ op,商EAX,余EDX |
无定义 | 无定义 | 无定义 | 无定义 |
关键图解:有符号与无符号乘除法的寄存器使用
flowchart TD
A[开始乘法/除法] --> B{MUL/IMUL 还是 DIV/IDIV?};
B --> C[MUL 或 IMUL<br>(单操作数形式)];
C --> D[“操作数 × EAX”];
D --> E[“结果存于 EDX:EAX<br>(高64位:低64位)”];
B --> F[DIV 或 IDIV];
F --> G[“被除数必须预置于 EDX:EAX<br>(高64位:低64位)”];
G --> H[“操作数作为除数”];
H --> I[“商存入 EAX, 余数存入 EDX”];
运算示例:
; 加减法
MOV EAX, 10
ADD EAX, 5 ; EAX=15, CF=0, ZF=0, SF=0
SUB EAX, 20 ; EAX=-5 (FFFFFFFBh), CF=1(借位), ZF=0, SF=1
; 自增自减
INC dword ptr [wVal] ; 注意:操作数大小需明确
; 取负
MOV EBX, 8
NEG EBX ; EBX=-8, CF=1
; 乘法
MOV EAX, 5000
MOV EBX, 1000
IMUL EBX ; 有符号乘: EDX:EAX = 5,000,000
; 或使用双操作数形式
IMUL ECX, EBX, 200 ; ECX = EBX * 200
; 除法(务必先设置EDX)
MOV EAX, 100 ; 被除数低32位
MOV EDX, 0 ; 被除数高32位清零(对于无符号除)
MOV EBX, 9 ; 除数
DIV EBX ; EAX=11(商), EDX=1(余数)
3. 移位与循环移位指令
这些指令高效,常用于乘除2的幂、位操作和数据提取。
| 指令 | 格式 | 作用图示/描述 | 移出的位去哪了? | 典型用途 |
|---|---|---|---|---|
| SHL/SAL | SHL dest, count |
逻辑/算术左移:CF ← [ ← dest ← ] ← 0 |
进入 CF | 无符号数 × \(2^n\) |
| SHR | SHR dest, count |
逻辑右移:0 → [ → dest → ] → CF |
进入 CF | 无符号数 ÷ \(2^n\) |
| SAR | SAR dest, count |
算术右移:MSB → [ → dest → ] → CF |
进入 CF | 有符号数 ÷ \(2^n\)(保持符号) |
| ROL | ROL dest, count |
循环左移:CF ← [ ← dest ← ] ←(首尾相连) |
同时进入 CF 和操作数另一端 | 位重组,高位与低位互换 |
| ROR | ROR dest, count |
循环右移:→ dest → ] → CF →(首尾相连) |
同时进入 CF 和操作数另一端 | 位重组,低位与高位互换 |
; 移位示例
MOV AL, 10110001b ; 0xB1
SHL AL, 1 ; AL=01100010b (0x62), CF=1 (乘以2,溢出)
MOV BL, 10001100b ; 0x8C (-116有符号,140无符号)
SAR BL, 2 ; BL=11100011b (0xE3, -29有符号), CF=0 (有符号除以4,向下舍入)
SHR BL, 2 ; BL=00100011b (0x23, 35无符号), CF=1 (无符号除以4)
; 循环移位示例
MOV AH, 11000011b
ROL AH, 3 ; AH=00011110b, CF=0 (将高3位循环移至低3位)
4. 比较与位测试指令
| 指令 | 格式 | 作用 | 实质 |
|---|---|---|---|
| CMP | CMP op1, op2 |
比较 op1 - op2,根据结果设置标志,不保存结果 |
隐式的 SUB,用于条件判断 |
| TEST | TEST op1, op2 |
对两个操作数做逻辑与,根据结果设置标志,不保存结果 | 隐式的 AND,用于测试特定位 |
CMP EAX, 10 ; 若EAX=10,则ZF=1;若EAX<10(无符号),则CF=1
TEST EBX, 1 ; 测试EBX最低位是否为1(奇偶),若为0则ZF=1
三、 关键编程提示与调试技巧
- 明确操作数大小:使用
BYTE PTR、WORD PTR等修饰符避免歧义。例如INC BYTE PTR [esi]。 - 除法前的准备:进行
DIV/IDIV前,务必正确设置EDX:EAX。无符号除用XOR EDX, EDX清零;有符号除用CDQ扩展。 - 善用LEA:
LEA可用于快速进行整数运算(如LEA EAX, [EBX + ECX*4 + 10]),它不访问内存,只计算地址。 - 标志位是核心:理解
JC(有进位跳)、JZ(为零跳)、JO(溢出跳)、JS(为负跳)等条件跳转指令依赖的标志。 - 调试观察:在调试器中(如VS或OllyDbg),单步执行并密切关注EFLAGS寄存器的变化,这是理解指令行为最直接的方法。
本文来自博客园,作者:ffff5,转载请注明原文链接:https://www.cnblogs.com/ffff5/p/19375438

浙公网安备 33010602011771号