简介 每种汇编语言都有进行操作数移位的指令,移位和循环移位指令在控制硬件设备、加密数据以及实现高速的图形操作时特别有用。本章讲述如何进行移位和循环移位操作以及使用移位操作进行高效的乘法和除法运算。
移位和循环移位指令
移位和循环移位的应用
乘法和除法指令
扩展加法和减法
ASCII和未压缩十进制算术指令
压缩十进制算术指令
移位和循环移位指令 Intel 提供了多种移位指令,下表中所有的移位指令都影响溢出标志和进位标志。
逻辑移位和算数移位 对于一个数字来说有两种最基本的移位操作。
SHL 指令 SHL指令对目的操作数执行逻辑左移操作,最低位以0填充,移出的最高位送入进位标志 CF,原来进位标志位中的值将丢失。
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代替,最低位被复制到进位标志中,原来的进位标志值丢失。
SAL 和 SAR 指令 SAL指令与SHL指令等价。SAR指令对目的的操作数执行算术右移操作。
ROL 指令 ROL指令在向左移动一位后,把最高位同时复制到进位标志和最低位中。其指令格式与SHL指令相同。
RCL 和 RCR 指令 RCL指令在每位左移一位后,把进位标志复制到最低有效位中,把最高有效位复制到进位标志中。
RCR 指令:RCR指令再每位向右移动一位后,把进位标志复制到最高有效位中并把最低有效位复制到进位标志中。
SHLD/SHRD指令 SHLD 和 SHRD 指令是从 Intel 386 处理器开始引入的。
SHLD 指令把目的操作数左移指定的位数,左移空出来的位用源操作数的高位来填充。指令对源操作数没有任何影响,但是符号标志、零标志、辅助进位标志、奇偶标志和进位标志都受影响。
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
把 ESI 的值设置为 array 的偏移
把最高位[esi+8]处的双字右移一位,此时最低位复制到进位标志(CF)中,最高位补0。
把[esi+4]的值右移一位,最高位自动以进位标志(CF)的值填充,最低位复制到进位标志中。
把[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的时间戳如下:
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,就置进位和溢出标志位。
IMUL 指令 IMUL(有符号乘法)指令在执行有符号整数的乘法运算,保留了乘积的符号位。IMUL指令在 IA-32 指令集中有三种格式:单操作数、双操作数和三操作数。
单操作数
和 MUL 指令一样,一般情况下不会发生溢出。如果进位和标志位置位了,表明高半部分 DX 或 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 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位无符号数整数除法运算。指令格式如下:
有符号整数除法 有符号除法和无符号除法几乎是完全相同的,唯一的不同在于:在进行除法操作之前,需要进行符号扩展。
符号扩展指令(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
算术表达式的实现 例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 指令
例子 实现两个任意同样尺寸的整数相加
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 指令
ASCII和未压缩十进制算术指令 未压缩十进制算术指令主要功能:将ASCII值转换为未压缩的值,之后再对该值进行调整。
指令集中有4条指令可以处理这一类的 ASCII 加法、减法、乘法和除法。
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,对其他标志均无定义。
案例:
ASCII码值 '8' - '9' = 00FFh
AL 的低 4 位在 A~F 之间,AF=1,AL-6=0F9h,AH=0FFh
AL 高4位清零,AL=09h
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指令
AAD指令
压缩十进制算术指令 在压缩十进制整数中,每个字节存储两个十进制数字,每个十进制数字用 4 个数据位来表示,如果压缩十进制数字的个数是偶数,最高位以 0 填充。
压缩十进制整数至少有以下两方面的优点:
数字几乎可以有任意数目的有效位,这使得执行高精度运算成为可能。
把压缩的十进制数转换成 ASCII 是相当简单的。
有两条指令 DAA(加法后进行十进制数调整)和DAS(减法后进行十进制数调整)可用于调整压缩十进制数字加法和减法的运算结果。对于乘法和除法不存在这样的指令。
DAA指令
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指令
编程练习(略) 参考 https://www.zhihu.com/question/31634405/answer/2581244793 https://zhuanlan.zhihu.com/p/22976065/