80386汇编-整数算数指令
9unk Lv5

简介

每种汇编语言都有进行操作数移位的指令,移位和循环移位指令在控制硬件设备、加密数据以及实现高速的图形操作时特别有用。本章讲述如何进行移位和循环移位操作以及使用移位操作进行高效的乘法和除法运算。

  • 移位和循环移位指令
  • 移位和循环移位的应用
  • 乘法和除法指令
  • 扩展加法和减法
  • ASCII和未压缩十进制算术指令
  • 压缩十进制算术指令

移位和循环移位指令

Intel 提供了多种移位指令,下表中所有的移位指令都影响溢出标志和进位标志。
1
2

逻辑移位和算数移位

对于一个数字来说有两种最基本的移位操作。

  • 第一种为逻辑移位,即以 0 填充最后移出的位。在下图中,一个字节逻辑右移一位,第 7 位被赋值0。
    3

  • 另一种移位类型称为算术移位,最后移出的位用数字原来的符号位填充。
    4

SHL 指令

SHL指令对目的操作数执行逻辑左移操作,最低位以0填充,移出的最高位送入进位标志 CF,原来进位标志位中的值将丢失。
5

SHL 语法:

1
SHL 目的操作数,移位位数

SHL 指令允许使用的操作数类型:

1
2
3
4
SHL reg,imm8
SHL mem,imm8
SHL reg,CL
SHL mem,CL

Intel 8086/8088 处理器要求 imm8 必须等于1,从 80286 及以上的处理器开始,imm8 可以是0~255之间的整数。在任何Intel处理器上,都可以使用CL存放移位位数。
上面的格式同样适用于 SHA,SAL,SAR,ROR,ROL,RCR 和 RCL 指令。

SHR 指令

SHR指令对目的操作数执行逻辑右移操作,移出的数据位以0代替,最低位被复制到进位标志中,原来的进位标志值丢失。
6

SAL 和 SAR 指令

SAL指令与SHL指令等价。SAR指令对目的的操作数执行算术右移操作。
7

ROL 指令

ROL指令在向左移动一位后,把最高位同时复制到进位标志和最低位中。其指令格式与SHL指令相同。
8

RCL 和 RCR 指令

RCL指令在每位左移一位后,把进位标志复制到最低有效位中,把最高有效位复制到进位标志中。
9

RCR 指令:RCR指令再每位向右移动一位后,把进位标志复制到最高有效位中并把最低有效位复制到进位标志中。
10

SHLD/SHRD指令

SHLD 和 SHRD 指令是从 Intel 386 处理器开始引入的。

SHLD 指令把目的操作数左移指定的位数,左移空出来的位用源操作数的高位来填充。指令对源操作数没有任何影响,但是符号标志、零标志、辅助进位标志、奇偶标志和进位标志都受影响。

1
SHLD 指令把目的操作数,源操作数,移位位数

SHRD 指令把目的操作数向右移动到指定的位数,空出来的位由源操作数的低位来填充。

1
SHRD 目的操作数,源操作数,移位位数

源操作数必须是寄存器。

移位和循环移位的应用

多双字移位

要对扩展精度整数(长整数)进行移位操作,可把它划分为字节数组、字数组或双字数组,然后再对该数组进行移位操作。在内存中存储数字时通常采取的方式是最低字节在最低的地址位上。下面的步骤以一个双数组为例,说明了如何把这样的数组右移一位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
;---------------------------
;程序名:Array_Shift.asm
;功能:演示如何对一个数组进行移位
;作者:9unk
;编写时间:2023-2-13
;----------------------------
INCLUDE Irvine32.inc
.data
ArraySize = 3
array DWORD ArraySize DUP(99999999h)

.code
start:
mov esi,0
shr array[esi + 8],1
rcr array[esi + 4],1
rcr array[esi],1
exit
END start
  1. 把 ESI 的值设置为 array 的偏移
  2. 把最高位[esi+8]处的双字右移一位,此时最低位复制到进位标志(CF)中,最高位补0。
  3. 把[esi+4]的值右移一位,最高位自动以进位标志(CF)的值填充,最低位复制到进位标志中。
  4. 把[esi+0]处的双字右移一位,其最高位自动以进位标志填充,其最低位复制到进位标志中。

二进制乘法

IA-32 的二进制乘法指令(MUL和IMUL)相对于其他机器指令来说是比较缓慢的。汇编程序员通常在遇到乘数是2的次幂的情况下,用SHL指令进行无符号数的乘法,以此提高运算效率。
例如:计算 123 x 36 = 123 x (2^5+2^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;---------------------------
;程序名:mul_div.asm
;功能:演示使用逻辑方法运算乘法和除法
;作者:9unk
;编写时间:2023-2-14
;----------------------------
INCLUDE Irvine32.inc

.code
start:
;乘法运算:123*36
mov eax,123
mov ebx,eax
shl eax,5
shl ebx,2
add eax,ebx
CALL DumpRegs
;除法运算:1024/8
mov eax,1024
shr eax,3
CALL DumpRegs
CALL WaitMsg
exit
END start

逻辑指令也可以用于除法。

显示二进制数的数据位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
;---------------------------
;程序名:BinToAsc.asm
;功能:使用 SHL 指令实现,将整数以bit数据的形式显示在屏幕上
;作者:9unk
;编写时间:2023-2-14
;----------------------------
INCLUDE Irvine32.inc
.data
ASCII DWORD 0ABh
BUFFER BYTE 32 dup(0),0

.code
main PROC
mov eax,ASCII
lea esi,BUFFER
call BinToAsc
mov edx,offset BUFFER
call WriteString
call crlf
exit
main ENDP

;---------------------------
BinToAsc PROC
;功能:把32位的整数转换为bit,并显示在屏幕上。
;入口参数:EAX=二进制数,ESI=缓冲区指针。
;返回值:buffer 填充二进制ASCII码值。
;--------------------------
push ecx
push esi
mov ecx,32
L1:
shl eax,1
mov BYTE PTR [esi],'0'
jnc L2
mov BYTE PTR [esi],'1'
L2:
inc esi
loop L1
pop esi
pop ecx
ret
BinToAsc ENDP
END main

分离 MS-DOS 文件的各个日期域

在 DOS 模式下,MS-DOS的功能 57h 在DX 中返回文件的日期戳,其中位 0~4 代表 0~31 之间的天数,位 5~8 代表月份,位 9~15 存放的年份。假设文件最后的修改时间是 1999 年 3 月 10 日,那么DX的时间戳如下:
11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
;---------------------------
;程序名:Time.asm
;功能:演示如何用移位分离各个时期域
;作者:9unk
;编写时间:2023-2-15
;----------------------------
INCLUDE Irvine32.inc
.data
time dw 0010011001101010B
day db 0
month db 0
year dw 0

.code
main PROC
mov dx,time
mov al,dl
and al,00011111b ;清除位5~7
mov day,al ;保存在变量 day 中
;
mov ax,dx
shr ax,5 ;右移5位
and al,00001111b ;清除位 4~7
mov month,al
;
mov al,dh
shr al,1 ;右移一位
mov ah,0 ;AH清零
add ax,1980 ;年份是相对于 1980 年的
mov year,ax ;保存的变量 year 中
exit
main ENDP
END main

乘法和除法指令

MUL 指令

MUL(无符号乘法)指令有三种格式:第一种将 8 位的操作数与 AL 相乘;第二种将 16 位 操作数与 AX 相乘;第三种将32位操作数与 EAX 相乘。乘数和被乘数大小必须相同,乘积的尺寸是乘数/被乘数大小的两倍。三种格式都既接受寄存器操作数,也接受内存操作数,但是不接受立即操作数。

1
2
3
MUL r/m8
MUL r/m16
MUL r/m32

指令中唯一的一个操作数是乘数。由于目的操作数(乘积)是乘数/被乘数大小的两倍,因此不会发生溢出。如果乘积的高半部分不为0,就置进位和溢出标志位。
12

IMUL 指令

IMUL(有符号乘法)指令在执行有符号整数的乘法运算,保留了乘积的符号位。IMUL指令在 IA-32 指令集中有三种格式:单操作数、双操作数和三操作数。

单操作数

13

和 MUL 指令一样,一般情况下不会发生溢出。如果进位和标志位置位了,表明高半部分 DX 或 EDX 存在值。

双操作数

14

三操作数

15

演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
;---------------------------
;程序名:imul.asm
;功能:演示乘法指令(有符号数)IMUL
;作者:9unk
;编写时间:2023-2-20
;----------------------------
INCLUDE Irvine32.inc
.data
word1 SWORD 4
dword1 SDWORD 4

.code
main PROC
;单操作数 16 位
mov al,48
mov bl,4
imul bl
;单操作数 32 位
mov eax,+4823424
mov ebx,-423
imul ebx
;双操作数
mov ax,-16
mov bx,2
imul bx,ax
imul bx,2
imul bx,word1
mov eax,-16
mov ebx,2
imul ebx,eax
imul ebx,2
imul ebx,dword1
;三操作数
imul bx,word1,-16
imul ebx,dword1,-16
imul ebx,dword1,-2000000000
main ENDP
END main

乘法操作的基准(性能)测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
;---------------------------
;程序名:ComepareMult.asm
;功能:演示位移指令、MUL、和IMUL指令时间
;作者:9unk
;编写时间:2023-2-20
;----------------------------
INCLUDE Irvine32.inc
.data
LOOP_COUNT = 0FFFFFFFFh
intval DWORD 5
startTime DWORD ?

.code
main PROC
call GetMseconds ;获取起始时间
mov startTime,eax

mov eax,intval
;call mult_by_shifting
call mult_by_mul

call GetMseconds ;获取停止时间
sub eax,startTime
call WriteDec ;显示用掉的时间
exit
main ENDP

mult_by_shifting PROC
;EAX 乘以 36,使用 SHL 指令
mov ecx,LOOP_COUNT
L1:
push eax
mov ebx,eax ;保存原始eax
shl eax,5
shl ebx,2
add eax,ebx
pop eax ;恢复 eax
loop L1
ret
mult_by_shifting ENDP

mult_by_mul PROC
;EAX 乘以 36,使用 MUL 指令
mov ecx,LOOP_COUNT
L1:
push eax
mov ebx,36
mul ebx
pop eax
loop L1
ret
mult_by_mul ENDP
END main

DIV 指令

DIV(无符号除法)指令执行 8 位、16位和32位无符号数整数除法运算。指令格式如下:
16
17

有符号整数除法

有符号除法和无符号除法几乎是完全相同的,唯一的不同在于:在进行除法操作之前,需要进行符号扩展。

符号扩展指令(CBW、CWD、CDQ)

  • CBW:扩展AL的符号位至AH中(字节符号扩展至字)
  • CWD:指令扩展AX的符号位至DX中(字符号扩展至双字)
  • CDQ:指令扩展EAX的符号位至EDX中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
;---------------------------
;程序名:idiv.asm
;功能:演示idiv指令的使用
;作者:9unk
;编写时间:2023-2-20
;----------------------------
INCLUDE Irvine32.inc
.data
byteVal SBYTE -48
wordVal SWORD -5000
dwordVal SDWORD -48
.code
main PROC
;例1:-48/5
mov al,byteVal
cbw
mov bl,5
idiv bl
;例2:-5000/256
mov ax,wordVal
cwd
mov bx,+256
idiv bx
;例3:5000/-256
mov eax,dwordVal
cdq
mov ebx,-256
idiv ebx
exit
main ENDP
END main

除法溢出

在除法操作生产的商太大,目的操作数无法容纳的时候,就会导致除法溢出,这会导致 CPU 触发一个中断,当前程序被终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;---------------------------
;程序名:div_over.asm
;功能:演示除法溢出
;作者:9unk
;编写时间:2023-2-20
;----------------------------
INCLUDE Irvine32.inc

.code
main PROC
mov ax,1000h
mov bl,10h
div bl
;
mov eax,1000h
cdq
mov ebx,10h
div ebx
main ENDP
END main

18

算术表达式的实现

例1

用汇编实现如下语句(32位无符号整数):
var4 = (var1 + var2) * var3;

1
2
3
4
5
mov eax,var1
add eax,var2
mul var3
jc tooBig
mov var4,eax

这里要注意,var4 是32位的。如果计算过程中出现溢出,最后var4存储的结果必然是错的。

例2

用汇编实现如下语句(32位无符号整数):
var4 = (var1 * 5) / (var2 - 3);

1
2
3
4
5
6
mov eax,var1
mov ebx,5
mul ebx
sub var2,3
div var2
mov var4,eax

mul 存放结果在 EDX:EAX 中,而 div 的被除数也是 EDX:EAX,因此不需要检查溢出。

例3

用汇编实现如下语句(32位有符号整数):
var4 = (var1 * -5) / (-var2 % var3);

1
2
3
4
5
6
7
8
9
10
11
12
;(-var2 % var3)
mov eax,var2
neg eax
cdq
idiv var3
mov ebx,edx ;备份
;(var1 * -5)
mov eax,-5
imul var1
;
idiv ebx
mov var4,eax

neg 取反,将整数变成负数。

扩展加法和减法

ADC 指令

19

例子

实现两个任意同样尺寸的整数相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
;---------------------------
;程序名:ExtAdd.asm
;功能:设计子函数 Extended_Add 实现任意两个同样大小的整数相加
;作者:9unk
;编写时间:2023-2-21
;----------------------------
INCLUDE Irvine32.inc
.data
num1 DWORD 123456789
lengthnum = ($-num1)/TYPE num1
num2 DWORD 123456789
result DWORD lengthnum+1 dup(0)

.code
main PROC
mov esi,OFFSET num1
mov edi,OFFSET num2
mov ebx,OFFSET result
mov ecx,lengthnum
call Extended_Add
;显示结果
mov eax,result+4
call WriteHex
mov eax,result
call WriteHex
call crlf
exit
main ENDP
;--------------------------------------------------------
Extended_Add PROC
;功能:实现任意两个同样大小的整数相加
;入口参数:ESI和EDI表示两个整数的指针;EBX表示计算结果的指针;ECX表示整数的大小
;出口参数:无
;--------------------------------------------------------
pushad
clc
L1:
mov eax,[esi]
adc eax,[edi]
pushfd
mov [ebx],eax
add esi,4
add edi,4
add ebx,4
popfd
loop L1

mov dword ptr [ebx],0
adc dword ptr [ebx],0
popad
ret
Extended_Add ENDP
END main

SBB 指令

20

ASCII和未压缩十进制算术指令

未压缩十进制算术指令主要功能:将ASCII值转换为未压缩的值,之后再对该值进行调整。
21

指令集中有4条指令可以处理这一类的 ASCII 加法、减法、乘法和除法。
22

ASCII 算术指令比二进制算术指令执行得慢,但它有如下有点:

  • 在进行算术运算之前无须进行字符串到二进制数值得转换
  • 可以使用假想得小数点进行实数运算,可避免使用浮点数时因近似产生的错误

AAA指令

AAA调整指令,将两个未压缩的十进制数字相加的和,转换成两个未压缩的十进制数字,最终可通过与 30h 进行 “或” 运算把它转换成对应的 ASCII 码。

1
2
3
4
5
6
7
8
9
10
.code
main PROC
mov ah,0
mov al,'8'
add al,'2'
aaa
or ax,3030h
INVOKE ExitProcess,0
main ENDP
END main

使用 AAA 指令的多字节加法

程序功能:将两个小数进行相加并输出结果(两个小数相加,我们可以把小数点忽略,在计算机中用整数相加,获得结果)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
;---------------------------
;程序名:ASCII_add.asm
;功能:对两个小数(ASCII码)进行加法运算,并输出结果。
;作者:9unk
;编写时间:2023-2-25
;----------------------------
INCLUDE Irvine32.inc

DECIMAL_OFFSET = 5
.data
;两个小数相加,我们可以把小数点忽略,在计算机中用整数相加。
decimal_one BYTE "100123456789765" ;1001234567.89765
decimal_two BYTE "900402076502015" ;9004020765.02015
sum BYTE (SIZEOF decimal_one+1) DUP(0),0

.code
main PROC

mov esi,SIZEOF decimal_one-1
mov edi,SIZEOF decimal_one
mov ecx,SIZEOF decimal_one
mov bh,0

L1: mov ah,0
mov al,decimal_one[esi]
add al,bh
aaa
mov bh,ah
or bh,30h
add al,decimal_two[esi]
aaa
or bh,ah
or bh,30h
or al,30h
mov sum[edi],al
dec esi
dec edi
loop L1
mov sum[edi],bh
;显示结果
mov edx,OFFSET sum
call WriteString
call Crlf
exit
main ENDP
END main

AAS指令

AAS指令两个未压缩十进制数相减,并把结果存储在 AL 中,最后再对 AL 中的结果进行调整,使其与 ASCII 数字表示一致。

指令运算逻辑(回顾)
这条指令在对 AL 中的差(由两个未组合的BCD码相减后的结果)进行调整,产生一个未组合的 BCD 码。调整方法如下:
(1)如 AL 中的低 4 位在 0~9 之间,且 AF 为 0,则跳转到(3)进行处理;
(2)如 AL 中的低 4 位在 A~F 之间,或 AF 为 1,则 AL<-(AL)-6,AH<-(AH)-1,且 AF 位置1;
(3)清除 AL 的高 4 位;
(4)AF 位送至 CF 位。
该指令影响标志位 AF 和 CF,对其他标志均无定义。

案例:

  1. ASCII码值 '8' - '9' = 00FFh
  2. AL 的低 4 位在 A~F 之间,AF=1,AL-6=0F9h,AH=0FFh
  3. AL 高4位清零,AL=09h
  4. CF=AF=1
    最终结果:AX=0FF09h,AL=9(结果),CF=AF=1

AAS指令逻辑:
‘8’ - ‘9’ 的结果变成 FF09 的逻辑:38h - 39h = 8 - 9,被减数 8 借位 = 18,18-9=9,因此 AL 的结果是 9

AAS指令只有被减数不够用的时候才会用,如果够用就不需要使用 AAS 指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
INCLUDE Irvine32.inc

.data
val1 BYTE '8'
val2 BYTE '9'

.code
main PROC
mov ah,0
mov al,val1
sub al,val2
jc next
or al,30h

next:
aas
sub al,10
movsx eax,al
call WriteInt
INVOKE ExitProcess,0
main ENDP
END main

AAM指令

51
52

AAD指令

53

压缩十进制算术指令

在压缩十进制整数中,每个字节存储两个十进制数字,每个十进制数字用 4 个数据位来表示,如果压缩十进制数字的个数是偶数,最高位以 0 填充。

压缩十进制整数至少有以下两方面的优点:

  • 数字几乎可以有任意数目的有效位,这使得执行高精度运算成为可能。
  • 把压缩的十进制数转换成 ASCII 是相当简单的。

有两条指令 DAA(加法后进行十进制数调整)和DAS(减法后进行十进制数调整)可用于调整压缩十进制数字加法和减法的运算结果。对于乘法和除法不存在这样的指令。

DAA指令

23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
;---------------------------
;程序名:AddPacked.asm
;功能:演示DAA指令
;作者:9unk
;编写时间:2023-2-25
;----------------------------
INCLUDE Irvine32.inc
.data
packed_1 WORD 4536h
packed_2 WORD 7207h
sum DWORD ?

.code
main PROC
mov sum,0
mov esi,0
;低字节运算
mov al,BYTE PTR packed_1[esi]
add al,BYTE PTR packed_2[esi]
daa
mov BYTE PTR sum[esi],al
inc esi
;高字节运算
mov al,BYTE PTR packed_1[esi]
adc al,BYTE PTR packed_2[esi]
daa
mov BYTE PTR sum[esi],al
;加进位
inc esi
mov al,0
adc al,0
mov BYTE PTR sum[esi],al
;显示结果
mov eax,sum
call WriteHex
call Crlf
exit
main ENDP
END main

DAS指令

24

编程练习(略)

参考

https://www.zhihu.com/question/31634405/answer/2581244793
https://zhuanlan.zhihu.com/p/22976065/

  • 本文标题:80386汇编-整数算数指令
  • 本文作者:9unk
  • 创建时间:2023-01-31 23:25:00
  • 本文链接:https://9unkk.github.io/2023/01/31/80386-hui-bian-zheng-shu-suan-shu-zhi-ling/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!