09 算术运算指令
1 算术运算指令格式(以加法为例)#
| add 语句的几种用法 | |
|---|---|
1.1 add reg, reg#
寄存器必须等宽!
上面的加法中,使用的寄存器有 8 位和 16 位的,所以不能直接相加。
Solution
如果一定要相加,则必须将较短的进行扩展:
寄存器的关系
mov eax, 12345678h
则 ax=5678h,ah=56h,al=78h,也就是
1.2 add var, constant#
必须手动声明变量宽度!
Attention
汇编语言中常数没有宽度。上面的例子中,编译器无法自动确定变量宽度,必须手动确定变量宽度为 byte ptr
设 ds=2000h,且从地址 2000h:1000h 起存放了下面四个字节:
Attention
汇编语言中,由于使用后缀 h 来声明 16 进制数,当这个 16 进制数开头恰好是字母时,编译器无法识别这是一个变量还是一个数。
所以,增加一个前缀 0 来说明这是一个数而不是一个变量。
| 同一个地址可以指向宽度不同的对象 | |
|---|---|
1.3 不存在 add var, var#
Hint
由于 Intel CPU 的硬件限制,一个 step 中只能访问一个地址的内存,因此无法直接将两个 var 相加
| 错误和对应的修正 | |
|---|---|
1.3.1 编译器如何处理变量相加#
| visual c++ 无优化 | |
|---|---|
Note
- 上述的汇编语言其实可以压缩到两步
- [00424a30]这里相当于省略了- ds:
2 加法和减法#
| 加法指令 | add | inc | adc | ||
|---|---|---|---|---|---|
| 含义 | 加法 | 自增 | 带进位加法 | ||
| 备注 | 不影响 CF | ||||
| 减法指令 | sub | dec | sbb | neg | cmp | 
| 含义 | 减法 | 自减 | 带借位减法 | 符号相反数 | 减法比较 | 
| 备注 | 不影响 CF | 相当于普通减法 | 不保存计算结果结果 | 
乘除法的语法比较复杂,本次课不会涉及
2.1 inc 自增#
- 比 add ax, 1机器码更短,执行速度更快
- 不会导致 CF的改变,但是会影响别的标志位
| 更短的代码 | |
|---|---|
- 上面的代码中,先使用 inc cx对下一次循环做准备,然后再进行跳转判断,减少了跳转语句
dec同理
2.2 adc 带进位加法#
| 不使用 32 位寄存器完成大数加法 12345678h+5678FFFFh | |
|---|---|
adc a, b ==> a = a + b + CF
2.2.1 用 adc 实现更大的加法#
| 使用数组表示的大数加法 | |
|---|---|
- 每次都直接使用 adc,类似接力的方法
2.3 sbb 带借位减法 (subtract with borrow)#
| sbb: 56781234h-1111FFFFh | |
|---|---|
- sbb a, b ==> a = a - b - CF
- 运算时应该先算低位产生借位,然后再用 sbb得到高位考虑借位的结果
2.4 neg 相反数#
Attention
应当当作普通的减法指令来看待,会影响标志位
2.5 cmp 减法比较#
cmp和sub的区别在于,进行了减法,保留了对标志位的影响,但是丢弃了结果
- jg, jl, jge, jle是有符号数比较相关跳转指令- jg: SF==OF && ZF==0
- jge: SF==OF
- jl: SF!=OF
- jle: SF!=OF || ZF==1
 
- ja, jb, jae, jbe是无符号数比较相关跳转指令- jb: CF=1(jump if below)
- ja: CF=0 & ZF=0(jump if above)
 
Example
- 上述 ah是有符号数-1,所以ja会跳转而jg不会跳转
3 乘法和除法#
| 指令 | mul | imul | 
|---|---|---|
| 含义 | 无符号乘法 | 符号数乘法 | 
| 指令 | div | idiv | 
| 含义 | 无符号整除 | 符号数除法 | 
3.1 mul 无符号乘法#
| 位宽 | 8 * 8 -> 16 | 16 * 16 -> 32 | 32 * 32 -> 64 | 
|---|---|---|---|
| 指令 | mul src(r/m[8]) | mul src(r/m[16]) | mul src(r/m[32]) | 
| 隐含被乘数 | AL | AX | EAX | 
| 含义 | AX = AL * SRC | DX:AX = AX * SRC | EDX:EAX = EAX * SRC | 
3.1.1 8 位乘法#
- 另一个乘数一定是 AL
- 乘积一定是 AX
3.1.2 16 位乘法#
- 被乘数一定是 AX
- 乘积一定是 DX:AX
3.1.3 32 位乘法#
- 被乘数一定是 EAX
- 乘积一定是 EDX:EAX
mul ebx  ; edx:eax=eax*ebx
3.1.3.1 Example: 十进制数字符串转 int32#
每读出一个字符,转换成数字,将原来的值乘以十加上这个数
3.2 imul 符号数乘法指令#
| 类型 | imul src(r/m[8/16/32]) | imul src1(r[16/32]), src2(r/m[16/32]) | imul src1(r[16]), src2(r/m[16]), constant(imm[16]) | 
|---|---|---|---|
| 隐含被乘数 | AL/AX/EAX | 无 | 无 | 
| 含义 | AX = AL * SRC/DX:AX = AX * SRC/EDX:EAX = EAX * SRC | SRC1 = SRC1 * SRC2 | SRC1 = SRC2 * CONSTANT | 
| imul 可以包含 2 个或者 3 个操作数 | |
|---|---|
规范
- 第二个操作数可以是寄存器或变量
- 第三个操作数必须是常数
Bug
- 可能会造成乘法溢出,不像除法溢出那样,CPU 不会中断处理乘法溢出
- idiv没有类似的使用方法
3.3 div 无符号数除法#
| 位宽 | 16 / 8 -> 8 | 32 / 16 -> 16 | 64 / 32 -> 32 | 
|---|---|---|---|
| 指令 | div src(r/m[8]) | div src(r/m[16]) | div src(r/m[32]) | 
| 隐含被除数 | AX | DX:AX | EDX:EAX | 
| 含义 | AX / SRC = AL ... AH | DX:AX / SRC = AX ... DX | EDX:EAX / SRC = EAX ... EDX | 
3.3.1 16 位除 8 位得 8 位#
ax / 除数 = al..ah
3.3.2 32 位除 16 位得 16 位#
dx:ax / 除数 = ax..dx
3.3.3 64 位除 32 位得 32 位#
edx:eax / 除数 = eax..edx
3.3.4 除法溢出#
- 除以 0 会发生溢出,但有时候除以 1 也会导致商太大而无法保存到商寄存器中
3.3.5 Example: 二进制转十进制输出#
Attention
- 要使用 64 位除 32 位才行,否则会溢出
- 使用栈先入后出的性质,来将余数按照正确的顺序输出
- 堆栈只能进行 16 位或 32 位操作,所以即使 dl是需要的char,也要push dx
- edx在进行除法前必须清零!
3.4 idiv 符号数除法指令#
| 计算 -2/2 | |
|---|---|
Attention
除法中,如果除数为零或者商寄存器放不下都会发生除法溢出,此时 CPU 会在除法指令上面插入 int 00h 中断
4 浮点数运算#
4.1 FP 指令#
- fadd, fsub, fmul, fdiv是加减乘除指令
- fld将小数类型变量从内存载入 CPU 中的小数寄存器
- fild将整数类型转化为小数并载入小数寄存器
- fst将小数寄存器- st(0)保存到变量中
- fstp将小数寄存器- st(0)保存到变量中并弹出- st(0)
最早的 8086 不支持浮点数计算,而是由单独的配套 FPU 8087 进行计算
4.2 FP 寄存器#
- st(0), st(1), ..., st(7)形成堆栈的形式- 在 fld/fild的时候会将st(0)往后push到st(1)
 
- 在 
- 宽度均为 80-bit 的 long double类型
Attention
- 编译器会在任何 float 指令前后插入 wait,因为 8086 需要等待 8087 计算完成
- td中观察 FP 寄存器需要打开- View/Numeric Processor窗口
- 运行完可以在数据窗中看到 i,以 IEEE754 格式表示