当前位置: 首页 > news >正文

《汇编语言:基于X86处理器》第7章 整数运算(1)

本章将介绍汇编语言最大的优势之一:基本的二进制移位和循环移位技术。实际上,位操作是计算机图形学、数据加密和硬件控制的固有部分。实现位操作的指令是功能强大的工具,但是高级语言只能实现其中的一部分,并且由于高级语言要求与平台无关,所以这些指令在一定程度上被弱化了。本章将展示一些对移位操作的应用,包括乘除法的优化。

并非所有的高级编程语言都支持任意长度整数的运算。但是汇编语言指令使得它能够加减几乎任何长度的整数。本章还将介绍执行压缩十进制整数和整数字符串运算的专用指令。

7.1移位和循环移位指令

移位指令与第6章介绍的按位操作指令一起形成了汇编语言最显著的特点之一。位移动(bit shifting)意味着在操作数内向左或向右移动。x86处理器在这方面提供了相当丰富的指令集(表 7-1 ),这些指令都会影响溢出标志位和进位标志位。

7.1.1 逻辑移位和算术移位

移动操作数的位有两种方法。第一种是逻辑移位(logic shift),空出来的位用0填充。如下图所示,一个字节的数据向右移动一位。也就是说,每一位都被移动到其旁边的低位上。注意,位 7 被填充为 0:

下图所示为二进制数11001111逻辑右移一位,得到0110 0111。最低位移人进位标志位:

另一种移位的方法是算术移位(arithmeticshift),空出来的位用原数据的符号位填充:

例如,二进制数11001111,符号位为1。算术右移一位后,得到11100111:

7.1.2 SHL 指令

SHL(左移)指令使目的操作数逻辑左移一位,最低位用0填充。最高位移入进位标志位,而进位标志位中原来的数值被丢弃:

若将11001111左移1位,该数就变为10011110:

SHL的第一个操作数是目的操作数,第二个操作数是移位次数:

SHL destination, count

该指令可用的操作数类型如下所示:

SHL reg,imm8
SHL mem,imm8
SHL reg,CL
SHL mem,CL

x86处理器允许imm8为0~255中的任何整数。另外,CL寄存器包含的是移位计数。

上述格式同样适用于SHR、SAL、SAR、ROR、ROL、RCR和RCL指令。

示例 下列指令中,BL左移一位。最高位复制到进位标志位,最低位填充0:

mov bl, 8Fh ;BL = 10001111b

shl bl,1 ;CF = 1,BL= 00011110b

当一个数多次进行左移时,进位标志位保存的是最后移出最高有效位(MSB)的数值下例中,位7没有留在进位标志位中,因为,它被位6(0)替换了:

mov a1,10000000b

shl al,2 ;CF = 0; AL = 00000000b

同样,当一个数多次进行右移时,进位标志位保存的是最后移出最低有效位(LSB)的数值。

位元乘法 数值进行左移(向MSB移动)即执行了位元乘法(Bitwise Multiplication),例如,SHL可以通过2的幂进行乘法运算。任何操作数左移位,即将该数乘以2"。现将整数5左移一位则得到5x2'=10:

mov dl, 5 ;移动前: 0 0 0 0 0 1 0 1 = 5

shl dl, 1 ;移动后: 0 0 0 0 1 0 1 0 = 10

若二进制数00001010(十进制数10)左移两位,其结果与10乘以2相同

mov dl,10 ;移动前: 00001010 = 10

shl dl,2 ;移动后: 00101000 = 40

完整代码测试笔记:

;7.1.2.asm  7.1.2 SHL 指令  格式:SHL destination, count
;SHL reg,imm8
;SHL mem,imm8
;SHL reg,CL
;SHL mem,CLINCLUDE Irvine32.inc.code
main PROC;示例 下列指令中,BL左移一位。最高位复制到进位标志位,最低位填充0:mov al, 8Fh						    ;AL = 10001111bmov ebx, 1							;显示一个字节call WriteBinB						;显示二进制位call Crlf							;换行shl al, 1							;CF = 1,AL= 00011110bpushfcall WriteBinB						;显示二进制位popfcall Crlf;当一个数多次进行左移时,进位标志位保存的是最后移出最高有效位(MSB)的数值下例中,;位7没有留在进位标志位中,因为,它被位6(0)替换了:mov al, 10000000bshl al, 2call WriteBinB						 call Crlf			;位元乘法mov al, 5							;移动前: 0 0 0 0 0 1 0 1 = 5shl al, 1							;移动后: 0 0 0 0 1 0 1 0 = 10call WriteBinB						 call Crlf	shl al, 2							;移动后: 00101000 = 40call WriteBinB						 call CrlfINVOKE ExitProcess,0
main ENDP
END main

运行调试:

执行mov a1,10000000b 和 shl al,2 后

7.1.3 SHR 指令

SHR(右移)指令使目的操作数逻辑右移一位,最高位用0填充。最低位复制到进位标志位,而进位标志位中原来的数值被丢弃:

SHR与SHL的指令格式相同。在下面的例子中,AL中的最低位0被复制到进位标志位,而AL中的最高位用0填充:

mov al, 0D0h ;AL = 11010000b

shr al, 1 ;AL = 01101000b,CF=0

在多位移操作中,最后一个移出位0(LSB)的数值进入进位标志位:

mov al, 00000010b

shr al,2 ;AL = 00000000b,CF=1

位元除法 数值进行右移(向LSB移动)即执行了位元除法(BitwiseDivision)。将个无符号数右移n位,即将该数除以2"。下述语句将32除以2,结果为16:

mov dl, 32 ;移动前: 0 0 1 0 0 0 0 0 = 32

shr dl, 1 ;移动后: 0 0 0 1 0 0 0 0 = 16

下例实现的是64除以2:

mov al, 01000000b ;AL = 64

shr al, 3 ;除以8,AL = 00001000b

用移位的方法实现有符号数除法可以使用SAR指令,因为该指令会保留操作数的符号位。

完整代码测试笔记:

;7.1.3.asm  7.1.3 SHR 指令  格式:SHR destination, count
;SHR reg,imm8
;SHR mem,imm8
;SHR reg,CL
;SHR mem,CLINCLUDE Irvine32.inc.code
main PROC;AL中的最低位0被复制到进位标志位,而AL中的最高位用0填充:mov al, 0D0h						;AL = 11010000bshr al, 1							;AL = 01101000b,CF = 0mov ebx, 1							;显示一个字节call WriteBinB					    ;显示二进制位call Crlf							;换行;在多位移操作中,最后一个移出位0(LSB)的数值进入进位标志位:mov al, 00000010bshr al, 2							;AL = 00000000b,CF=1pushfcall WriteBinB					    ;显示二进制位popfcall Crlf;位元除法  数值进行右移(向LSB移动)即执行了位元除法(BitwiseDivision)。将个无符号数右移n位,即将该数除以2"。mov al, 32							;移动前: 0 0 1 0 0 0 0 0 = 32shr al, 1							;移动后: 0 0 0 1 0 0 0 0 = 16call WriteBinBcall Crlf;下例实现的是64除以2:mov al, 01000000b				    ;AL = 64shr al, 3							;除以8,AL = 00001000bcall WriteBinB						 call Crlf	INVOKE ExitProcess,0
main ENDP
END main

运行调试:

7.1.4 SAL和 SAR 指令

SAL(算术左移)指令的操作与SHL指令一样。每次移动时,SAL都将目的操作数中的每一位移动到下一个最高位上。最低位用0填充;最高位移入进位标志位,该标志位原来的值被丢弃:

如,二进制数11001111算术左移一位,得到10011110:

SAR(算术右移)指令将目的操作数进行算术右移:

SAL与SAR指令的操作数类型与SHL和SHR指令完全相同。移位可以重复执行,其次数由第二个操作数给出的计数器决定:

SAR destination, count

下面的例子展示了SAR是如何复制符号位的。执行指令前AL的符号位为负,执行指令后该位移动到右边的位上:

mov al, 0F0h ;AL=11110000b(-16)

sar al,1 ;AL=11111000b(8),CF=0

有符号数除法使用SAR指令,就可以将有符号操作数除以2的。下例执行的是-128除以2,商为-16:

mov dl, -128 ;DL = 10000000b

sar dl, 3 ;DL = 11110000b

AX符号扩展到EAX 设AX中为有符号数,现将其符号位扩展到EAX。首先把EAX左移16位,再将其算术右移16位:

mov ax, -128					;EAX = ????FF80h
shl eax, 16						;EAX = FF800000h
sar eax, 16						;EAX = FFFFFF80h

完整代码测试笔记

;7.1.4.asm  7.1.4 SAL和 SAR 指令  格式:SAL destination, count
;算术左移和算术右移,保留符号位INCLUDE Irvine32.inc.code
main PROC;SAL都将目的操作数中的每一位移动到下一个最高位上。最低位用0填充;最高位移入进位标志位,该标志位原来的值被丢弃:mov ebx, 1							    ;显示一个字节mov al,11001111b				        ;AL = 11001111bcall WriteBinB					        ;显示二进制位call Crlf								;换行sal al, 1								;CF = 1,AL= 1001 1110bpushfcall WriteBinB						popfcall Crlfsal al, 2								;CF = 0,AL= 0111 1000b   符号位也被移掉了call WriteBinB						call Crlf;下面的例子展示了SAR是如何复制符号位的。执行指令前AL的符号位为负,执行指令后该位移动到右边的位上:mov al, 0F0h						    ;AL=11110000b(-16)sar al, 1								;AL=11111000b(8),CF=0			call WriteBinB						 call Crlf			;有符号数除法使用SAR指令,就可以将有符号操作数除以2的。下例执行的是-128除以2,商为-16:mov al, -128						    ;AL = 10000000bsar al, 3								;AL = 11110000bcall WriteBinB						 call Crlf	;AX符号扩展到EAX 设AX中为有符号数,现将其符号位扩展到EAX。首先把EAX左移16位,再将其算术右移16位:mov ax, -128						    ;EAX = ????FF80hcall WriteBinB						 call Crlfshl eax, 16							    ;EAX = FF800000hcall WriteBinB						 call Crlfsar eax, 16							    ;EAX = FFFFFF80hcall WriteBinB						 call CrlfINVOKE ExitProcess,0
main ENDP
END main

运行调试:

7.1.5 ROL 指令

以循环方式来移位即为位元循环(Bitwise Rotation)。一些操作中,从数的一端移出的位立即复制到该数的另一端。还有一种类型则是把进位标志位当作移动位的中间点。

ROL(循环左移)指令把所有位都向左移。最高位复制到进位标志位和最低位。该指令格式与SHL指令相同:

位循环不会丢弃位。从数的一端循环出去的位会出现在该数的另一端。在下例中,请注意最高位是如何复制到进位标志位和位0的:

mov al, 40h							    ;AL = 0100 0000b
rol al, 1								;AL = 1000 0000b, CF = 0
rol al, 1								;AL = 0000 0001b, CF = 1
rol al, 1								;AL = 0000 0010b, CF = 0

循环多次 当循环计数值大于1时,进位标志位保存的是最后循环移出MSB的位:

mov al, 00100000b
rol al, 3								;CF = 1, AL =  0000 0001b

位组交换 利用ROL可以交换一个字节的高四位(位4~7)和低四位(位0~3)。例如,26h向任何方向循环移动4位就变为62h:

mov al, 26h
rol al, 4								;AL = 62h

当多字节整数以四位为单位进行循环移位时,其效果相当于一次向右或向左移动一个十六进制位。例如,将6A4Bh反复循环左移四位,最后就会回到初始值:

mov ax, 6A4Bh
rol ax, 4								;AX = A4B6h
rol ax, 4								;AX = 4B6Ah
rol ax, 4								;AX = B6A4h
rol ax, 4								;AX = 6A4Bh

完整代码测试笔记:

;7.1.5.asm  7.1.5 ROL 指令  ROL(循环左移)指令把所有位都向左移。INCLUDE Irvine32.inc.code
main PROC;位循环不会丢弃位。从数的一端循环出去的位会出现在该数的另一端。;在下例中,请注意最高位是如何复制到进位标志位和位0的:mov ebx, 1							    ;显示一个字节mov al, 40h							    ;AL = 0100 0000bcall WriteBinB					        ;显示二进制位call Crlf								;换行rol al, 1								;AL = 1000 0000b, CF = 0call WriteBinB						call Crlfrol al, 1								;AL = 0000 0001b, CF = 1call WriteBinB						call Crlfrol al, 1								;AL = 0000 0010b, CF = 0call WriteBinB						call Crlf;循环多次 当循环计数值大于1时,进位标志位保存的是最后循环移出MSB的位:mov al, 00100000brol al, 3								;CF = 1, AL =  0000 0001bcall WriteBinB						 call Crlf			;位组交换 利用ROL可以交换一个字节的高四位(位4~7)和低四位(位0~3)。例如,26h向任何方向循环移动4位就变为62h:mov al, 26hrol al, 4								;AL = 62h;当多字节整数以四位为单位进行循环移位时,其效果相当于一次向右或向左移动一个十六进制位。;例如,将6A4Bh反复循环左移四位,最后就会回到初始值:mov ax, 6A4Bhrol ax, 4								;AX = A4B6hrol ax, 4								;AX = 4B6Ahrol ax, 4								;AX = B6A4hrol ax, 4								;AX = 6A4BhINVOKE ExitProcess,0
main ENDP
END main

运行调试:

7.1.6 ROR 指令

ROR(循环右移)指令把所有位都向右移,最低位复制到进位标志位和最高位。该指令格式与SHL指令相同:

在下例中,请注意最低位是如何复制到进位标志位和结果的最高位的:

mov al, 01h							    ;AL = 0000 0001b
ror al, 1								;AL = 1000 0000b, CF = 1
ror al, 1								;AL = 0100 0000b, CF = 0

循环多次 当循环计数值大于1时,进位标志位保存的是最后循环移出LSB的位:

mov al, 00000100b
ror al, 3								;AL = 1000 0000b, CF = 1

完整代码测试笔记

;7.1.6.asm  7.1.6 ROR 指令  格式:ROR destination, count
;ROR(循环右移)指令把所有位都向右移,最低位复制到进位标志位和最高位。INCLUDE Irvine32.inc.code
main PROC;在下例中,请注意最低位是如何复制到进位标志位和结果的最高位的:mov ebx, 1							    ;显示一个字节mov al, 01h						        ;AL = 0000 0001bcall WriteBinB						    ;显示二进制位call Crlf								;换行ror al, 1								;AL = 1000 0000b, CF = 1pushfcall WriteBinB						 popfcall Crlfror al, 1								;AL = 0100 0000b, CF = 0call WriteBinB						 call Crlf;循环多次 当循环计数值大于1时,进位标志位保存的是最后循环移出LSB的位:mov al, 00000100bror al, 3								;AL = 1000 0000b, CF = 1call WriteBinB						 call Crlf			INVOKE ExitProcess,0
main ENDP
END main

运行调试:

7.1.7 RCL和 RCR 指令

RCL(带进位循环左移)指令把每一位都向左移,进位标志位复制到LSB,而MSB复制到进位标志位:

如果把进位标志位当作操作数最高位的附加位,那么RCL就成了循环左移操作。下面的例子中,CLC指令清除进位标志位。第一条RCL指令将BL最高位移入进位标志位,其他位都向左移一位。第二条RCL指令将进位标志位移人最低位,其他位都向左移一位:

clc						;CF = 0
mov bl, 88h			    ;CF, BL = 0 10001000b
rcl bl, 1				;CF, BL = 1 00010000b
rcl bl, 1				;CF, BL = 0 00100001b

从进位标志位恢复位 RCL可以恢复之前移入进位标志位的位。下面的例子把testval的最低位移入进位标志位,并对其进行检查。如果testval的最低位为1,则程序跳转;如果最低位为0,则用RCL将该数恢复为初始值:

.data
testval BYTE 01101010b
.code
shr testval, 1						;将LSB 移入进位标志位
jc exit								;如果该标志位置1,则退出
rcl testval, 1						;否则恢复该数原值

RCR指令 RCR(带进位循环右移)指令把每一位都向右移,进位标志位复制到MSB,而 LSB 复制到进位标志位:

从上图来看,RCL指令将该整数转化成了一个9位值,进位标志位位于LSB的右边。下面的示例代码用STC将进位标志位置1,然后,对AH寄存器执行一次带进位循环右移操作:

stc									;CF = 1
mov ah, 10h						    ;AH, CF = 00010000 1
rcr ah, 1							;AH, CF = 10001000 0 

完整代码测试笔记

;7.1.7.asm  7.1.7 RCL和 RCR 指令
;RCL(带进位循环左移)指令把每一位都向左移,进位标志位复制到LSB,而MSB复制到进位标志位:
;RCR(带进位循环右移)指令把每一位都向右移,进位标志位复制到MSB,而 LSB 复制到进位标志位:INCLUDE Irvine32.inc.data
testval BYTE 01101010b.code
main PROC;示例mov ebx, 1							;显示一个字节clc									;CF = 0mov al, 88h							;CF, BL = 0 10001000bcall WriteBinB					    ;显示二进制位call Crlf							;换行rcl al, 1							;CF, BL = 1 00010000bcall WriteBinB						call Crlf								rcl al, 1							;CF, BL = 0 00100001bcall WriteBinB						call Crlf								;从进位标志位恢复位  RCL可以恢复之前移入进位标志位的位。;下面的例子把testval的最低位移入进位标志位,并对其进行检查。shr testval, 1						;将LSB 移入进位标志位jc quit								;如果该标志位置1,则退出rcl testval, 1						;否则恢复该数原值mov al, testvalcall WriteBinB						 call Crlf			;RCR(带进位循环右移)指令把每一位都向右移,进位标志位复制到MSB,而 LSB 复制到进位标志位:stc									;CF = 1mov al, 10h							;AL, CF = 00010000 1rcr al, 1							;AL, CF = 10001000 0 call WriteBinB						 call Crlf	quit:INVOKE ExitProcess,0
main ENDP
END main

运行调试:

7.1.8 有符号数溢出

如果有符号数循环移动一位生成的结果超过了目的操作数的有符号数范围,则溢出标志位置1。换句话说,即该数的符号位取反。下例中,8位寄存器中的正数(+127)循环左移后变为负数(-2):

mov al, +127						;AL = 01111111b
rol al, 1							;OF = 1,AL= 11111110b

同样,-128向右移动一位,溢出标志位置1。AL中的结果(+64)符号位与原数相反:

mov al, -128						;AL = 10000000b	
shr al, 1							;OF = 1, AL = 01000000b

如果循环移动次数大于1,则溢出标志位无定义。

;7.1.8.asm  7.1.8 有符号数溢出INCLUDE Irvine32.inc.code
main PROC;8位寄存器中的正数(+127)循环左移后变为负数(-2):mov ebx, 1							;显示一个字节mov al, +127						;AL = 01111111bcall WriteBinB					    ;显示二进制位call Crlf							;换行rol al, 1							;OF = 1,AL= 11111110bpushfcall WriteBinB						 popfcall Crlf;同样,-128向右移动一位,溢出标志位置1。AL中的结果(+64)符号位与原数相反:mov al, -128						;AL = 10000000bcall WriteBinB						 call Crlf			shr al, 1							;OF = 1, AL = 01000000bcall WriteBinB						 call Crlf	INVOKE ExitProcess,0
main ENDP
END main

运行调试:

7.1.9 SHLD/SHRD 指令

SHLD(双精度左移)指令将目的操作数向左移动指定位数。移动形成的空位由源操作数的高位填充。源操作数不变,但是符号标志位、零标志位、辅助进位标志位、奇偶标志位和进位标志位会受影响:

SHLD dest, source, count

下图展示的是 SHLD 执行移动一位的过程。源操作数的最高位复制到目的操作数的最低位上。目的操作数的所有位都向左移动:

SHRD(双精度右移)指令将目的操作数向右移动指定位数。移动形成的空位由源操作数的低位填充:

SHRD dest,source,count

下图展示的是SHRD 执行移动一位的过程:

下面的指令格式既可以应用于SHLD也可以应用于SHRD。目标操作数可以是寄存器或内存操作数;源操作数必须是寄存器;移位次数可以是CL寄存器或者8位立即数:

SHLD reg16, reg16, CL/imm8
SHLD mem16, reg16, CL/imm8
SHLD reg32, reg32, CL/imm8
SHLD mem32, reg32, CL/imm8

示例1 下述语句将wval左移4位,并把AX的高4位插入wval的低4位:

.data
wval WORD 9BA6h
.code
mov ax, 0AC36h
shld wval, ax, 4				;wval = BA6Ah

数据移动过程如下图所示:

示例2 下例中,AX右移4位,DX的低4位移入AX的高4位:

mov ax, 234Bh
mov dx, 7654h
shrd ax, dx, 4

为了在屏幕上重定位图像而必须将位元组左右移动时,可以用SHLD和SHRD来处理位映射图像。另一种可能的应用是数据加密,如果加密算法中包含位的移动的话。最后,对于很长的整数来说,这两条指令还可以用于快速执行其乘除法。

下面的代码示例展示了用SHRD如何将一个双字数组右移4位:

.data
array DWORD 648B2165h, 8C943A29h, 6DFA4B86h, 91F76C04h, 8BAF9857h
.codemov bl, 4								;移位次数mov esi, OFFSET array					;数组的偏移量mov ecx, (LENGTHOF array) - 1	        ;数组元素个数
L1:push ecx								;保存循环计数mov eax, [esi + TYPE DWORD]mov cl, bl								;移动次数shrd [esi], eax, cl						;EAX移入[ESI]的高位add esi, TYPE DWORD						;指向下一对双字pop ecx									;恢复循环计数loop L1shr DWORD PTR [esi], 4				    ;最后一个双字进行移位

完整代码测试笔记:

;7.1.9.asm  7.1.9  SHLD/SHRD 指令    格式:SHLD dest, source, count
;SHLD reg16, reg16, CL/imm8
;SHLD mem16, reg16, CL/imm8
;SHLD reg32, reg32, CL/imm8
;SHLD mem32, reg32, CL/imm8INCLUDE Irvine32.inc.data
wval WORD 9BA6h
array DWORD 648B2165h, 8C943A29h, 6DFA4B86h, 91F76C04h, 8BAF9857h.code
main PROC;示例1 下述语句将wval左移4位,并把AX的高4位插入wval的低4位:mov ax, 0AC36hshld wval, ax, 4							;wval = BA6Ah;示例2 下例中,AX右移4位,DX的低4位移入AX的高4位:mov ax, 234Bhmov dx, 7654hshrd ax, dx, 4								;ax = 4234h;下面的代码示例展示了用SHRD如何将一个双字数组右移4位:mov bl, 4									;移位次数mov esi, OFFSET array				        ;数组的偏移量mov ecx, (LENGTHOF array) - 1	            ;数组元素个数
L1:push ecx									;保存循环计数mov eax, [esi + TYPE DWORD]mov cl, bl									;移动次数shrd [esi], eax, cl						    ;EAX移入[ESI]的高位add esi, TYPE DWORD					        ;指向下一对双字pop ecx										;恢复循环计数loop L1shr DWORD PTR [esi], 4				        ;最后一个双字进行移位INVOKE ExitProcess,0
main ENDP
END main

运行调试:

示例1:

示例2:

数组移动前

移动后:

7.1.10 本节回顾

1.哪条指令将操作数的每一位都进行左移,并把最高位复制到进位标志位和最低位?

答:ROL指令

2.哪条指令将操作数的每一位都进行右移,并把最低位复制到进位标志位,而进位标志位复制到最高位?

答:RCR指令,带进位循环右移

3.哪条指令执行如下操作(CF 为进位标志位)?

执行前:CF AL=1 11010101

执行后:CF, AL=1 10101011

答:RCL指令, 带进位循环左移

4.执行指令 SHR AX,1时,进位标志位发生了怎样的变化?

答:进位标志位接收了AX(移位之前)的最低位。

5.挑战:编写一组指令,不使用SHRD指令,将AX的最低位移入 BX的最高位。然后,使用SHRD 指令实现相同的操作。

答:

;7.1.10_5.asm  7.1.10  本节回顾
;5.挑战:编写一组指令,不使用SHRD指令,将AX的最低位移入 BX的最高位。然后,使用SHRD 指令实现相同的操作。INCLUDE Irvine32.inc.code
main PROCmov ax, 1234hmov bx, 5678hshr ax, 1					;AX移位到进位标志位rcr bx, 1					;进位标志位移位到BX;SHRD 指令mov ax, 1234hmov bx, 5678hshrd bx, ax, 1			 INVOKE ExitProcess,0
main ENDP
END main

6.挑战:计算EAX中32位数奇偶性的方法之一是利用循环把该数的每一位都移入进位标志位,然后计算进位标志位置1的次数。编写代码实现上述功能,并根据结果设置奇偶标志位。

答:

;7.1.10_6.asm  7.1.10  本节回顾
;6.挑战:计算EAX中32位数奇偶性的方法之一是利用循环把该数的每一位都移入进位标志位,然后计算进位标志位置1的次数。
;编写代码实现上述功能,并根据结果设置奇偶标志位。.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO, dwExitCode: DWORD.code
main PROCmov ecx, 32			        ;循环32次mov bl, 0				    ;统计1的个数mov eax, 15
L1: shr eax, 1			        ;右移,最后一位进入进位标志位jnc L2					    ;进位标志位不为1,跳转进入下一位inc bl					    ;进位标志位为1, bl加1
L2:loop L1;若BL为奇数,清除奇偶示志位;若BL为偶数,奇偶标志位置1shr bl, 1jc odd						;进位跳转mov bh, 0or bh, 0					;PF = 1 偶校验jmp next
odd:mov bh, 1or bh, 1					;PF = 0 奇校验
next:INVOKE ExitProcess,0
main ENDP
END main

7.2 移位和循环移位的应用

当程序需要将一个数的位从一部分移动到另一部分时,汇编语言是非常合适的工具。有时,把数的位元子集移动到位0,便于分离这些位的值。本节将展示一些易于实现的常见移位和循环移位的应用。更多应用参见本章习题。

7.2.1 多个双字的移位

对于已经被分割为字节、字或双字数组的扩展精度整数可以进行移位操作。在此之前必须知道该数组元素是如何存放的。保存整数的常见方法之一被称为小端顺序(1ittle-endianorder)。其工作方式如下:将数组的最低字节存放到它的起始地址,然后,从该字节开始依序把高字节存放到下一个顺序的内存地址中。除了可以将数组作为字节序列存放外,还可以将其作为字序列和双字序列存放。如果是后两种形式,则字节和字节之间仍然是小端顺序因为x86 机器是按照小端顺序存放字和双字的。

下面的步骤说明了怎样将一个字节数组右移一位。

步骤1: 把位于[ESI+2]的最高字节右移一位,其最低位自动复制到进位标志位。

步骤2: 把[ESI+1]循环右移一位,即用进位标志位填充最高位,而将最低位移入进位标志位:

步骤3: 把[ESI循环右移一位,即用进位标志位填充最高位,而将最低位移入进位标志位:

步骤3完成后,所有的位都向右移动了一位:

下面的代码节选自Multishift.asm程序,实现的是上述3个步骤:

.data
ArraySize = 3
array BYTE ArraySize DUP(99h)			;每个半字节的值都是1001
.code
main PROCmov esi, 0shr array[esi+2], 1					;高字节rcr array[esi+1], 1					;中间字节,包括进位标志位rcr array[esi], 1					;低字节,包括进位标志位

虽然这个例子只有3个字节进行了移位,但是它能很容易被修改成执行字数组或双字数组的移位操作。利用循环,可以对任意大小的数组进行移位操作。

完整代码测试笔记

;Multishift.asm  7.2.1  多个双字的移位
;字节数组循环移位INCLUDE Irvine32.inc.data
ArraySize = 3
array BYTE ArraySize DUP(99h)			;每个半字节的值都是1001.code
main PROCmov ebx, 1							;显示一个字节mov edi, OFFSET arraymov ecx, LENGTHOF arraymov esi, 0;原始数据
L1:mov al, array[esi]call WriteBinB						;显示二进制位inc esiloop L1mov esi, 0shr array[esi+2], 1					;高字节rcr array[esi+1], 1					;中间字节,包括进位标志位rcr array[esi], 1					;低字节,包括进位标志位call Crlf							;换行mov esi,0mov ecx, LENGTHOF array;循环移位后显示
L2:mov al, array[esi]call WriteBinB						 					inc esiloop L2INVOKE ExitProcess,0
main ENDP
END main

运行调试:

循环移位后:

7.2.2 二进制乘法

有时程序员会压榨出任何可以获得的性能优势,他们会使用移位而非MUL指令来实现整数乘法。当乘数是2的幂时,SHL指令执行的是无符号数乘法。一个无符号数左移n位就是将其乘以 2"。其他任何乘数都可以表示为2的幂之和。例如,若将EAX中的无符号数乘以36,则可以将36写为2'+22,再使用乘法分配律:

下图展示了乘法123*36得到结果 4428 的过程:

请注意这里有个有趣的现象,乘数(36)的位2和位5都为1,而整数2和5又是需要移位的次数。利用这个现象,下面的代码片段使用SHL和ADD指令实现了123乘以36:

编程笔记

;7.2.2.asm   7.2.2   二进制乘法INCLUDE Irvine32.inc.code
main PROCmov eax, 123mov ebx, eaxshl eax, 5				;乘以 2的5次方shl ebx, 2				;乘以 2的2次方add eax, ebx			;乘积相加INVOKE ExitProcess,0
main ENDP
END main

运行调试:

作为本章的编程练习,要求读者把上例一般化,并编写一个过程,用移位和加法计算任意两个32位无符号整数的乘法。

7.2.3 显示二进制位

将二进制整数转换为ASCII码的位串,并显示出来是一种常见的编程任务。SHL指令适用于这个要求,因为每次操作数左移时,它都会把操作数的最高位复制到进位标志位。下面的 BinToAsc过程是该功能一个简单的实现:

;7.2.3.asm   7.2.3   显示二进制INCLUDE Irvine32.inc.data
binArray BYTE 32 DUP(?), 0.code
main PROC;调用测试mov eax, 123A5B97hmov esi, OFFSET binArraycall BinToAscmov edx, offset binArraycall WriteString						;控制台显示二进制字符串INVOKE ExitProcess,0
main ENDP
;------------------------------------------------------------
;将32位二进制整数转换为ASCII码的二进制形式
;接收:EAX=二进制整数,ESI为缓冲区指针
;返回:包含ASCII码二进制数字的缓冲区
;-----------------------------------------------------------
BinToAsc PROCpush ecxpush esimov ecx, 32							;EAX中的位数
L1:	shl eax, 1								;最高位移入进位标志位mov BYTE PTR [esi], '0'		        ;选择0作为默认数字jnc L2								;如果进位标志位为0,则跳转到L2, (无进位跳转指令)mov BYTE PTR [esi], '1'		        ;否则将1送入缓冲区
L2:	inc esi									;指向下一个缓冲区位置loop L1								;下一位进行左移pop esipop ecxret
BinToAsc ENDPEND main

运行调试:

转换后:

7.2.4 提取文件日期字段

当存储空间非常宝贵的时候,系统软件常常将多个数据字段打包为一个整数。要获得这些数据,应用程序就需要提取被称为位串(bitstring)的位序列。例如,在实地址模式下MS-DOS函数 57h用DX返回文件的日期戳。(日期戳显示的是该文件最后被修改的日期)其中,位0~位4表示的是1~31内的日期;位5~位8表示的是月份;位9~位15表示的是年份。如果一个文件最后被修改的日期是1999年3月10日,则DX寄存器中该文件的日期戳就如下图所示(年份以1980为基点):

要提取一个位串,就把这些位移到寄存器的低位部分,再清除掉其他无关的位。下面的代码示例从一个日期戳中提取日期字段,方法是:复制DL,然后屏蔽与该字段无关的位:

mov al, dl							;复制 DL
and al, 00011111b				    ;清除位5~位7
mov day, al							;结果存入变量day

要提取月份字段,就把位5~位8移到AL的低位部分,再清除其他无关位,最后把AL复制到变量中:

mov ax, dx							;复制 DX
shr ax, 5							;右移5 位
and al, 00001111b				    ;清除位4~位7
mov month, al						;结果存入变量month

年份字段(位9~位15)完全包含在DH寄存器中,将其复制到AL,再右移1位:

mov al, dh							;复制DH
shr al, 1							;右移1位
mov ah, 0							;将AH清零
add ax, 1980						;年份基点为1980
mov year, ax						;结果存入变量year

完整代码测试笔记

;7.2.4.asm   7.2.4 提取文件日期字段
;日期戳显示的是该文件最后被修改的日期
;其中,位0~位4表示的是1~31内的日期;位5~位8表示的是月份;位9~位15表示的是年份。INCLUDE Irvine32.inc.data
day BYTE ?
month BYTE ?
year WORD ?.code
main PROC;测试值dx = 01000110 11111011bmov dx, 0100011011111011b;day 提取日期字段mov al, dl							;复制 DLand al, 00011111b				    ;清除位5~位7mov day, al						    ;结果存入变量day;month  提取月份字段,就把位5~位8移到AL的低位部分,再清除其他无关位mov ax, dx							;复制 DXshr ax, 5							;右移5 位and al, 00001111b				    ;清除位4~位7mov month, al					    ;结果存入变量month;year	 年份字段(位9~位15)完全包含在DH寄存器中,将其复制到AL,再右移1位:mov al, dh							;复制DHshr al, 1							;右移1位mov ah, 0							;将AH清零add ax, 1980						;年份基点为1980mov year, ax						;结果存入变量yearINVOKE ExitProcess,0
main ENDP
END main

运行调试:

7.2.5 本节回顾

1.编写汇编语言指令,用二进制乘法计算EAX*24。

答:24 = 24+23 所以EAX*24 = EAX*( 24+23 )

;7.2.5_1.asm   1.编写汇编语言指令,用二进制乘法计算EAX*24。
;24 = 2的4次方+2的3次方INCLUDE Irvine32.inc.code
main PROCmov eax, 78mov ebx, eaxshl eax, 4				;乘以2的4次方shl ebx, 3				;乘以2的3次方add eax, ebx			;乘积相加INVOKE ExitProcess,0
main ENDP
END main

运行调试:

2.编写汇编语言指令,用二进制乘法计算 EAX*21。提示:21=24+22+20

答:EAX *21 = EAX*(24+22+20)

;7.2.5_2.asm  2.编写汇编语言指令,用二进制乘法计算 EAX*21。
;提示:21=2的4次方+2的2次方+2的0次方INCLUDE Irvine32.inc.code
main PROCmov eax, 77mov ebx, eaxmov edx, eaxshl eax, 4				;乘以2的4次方shl ebx, 2				;乘以2的2次方add eax, ebx			;乘积相加add eax, edxINVOKE ExitProcess,0
main ENDP
END main

运行调试:

3.若用 7.2.3 节的 BinToAsc 过程反向显示二进制位,应怎样修改该过程?

答,把shl eax, 1改成shr eax, 1即可。

4.文件目录项的时间戳字段结构为:位0~位4为秒,位5~位10为分钟,位11~位15为小时。编写指令序列,提取分钟字段,并将值复制到字节变量bMinutes中。

答:

;7.2.5_4.asm   4.文件目录项的时间戳字段结构为:位0~位4为秒,位5~位10为分钟,位11~位15为小时。
;编写指令序列,提取分钟字段,并将值复制到字节变量bMinutes中。INCLUDE Irvine32.inc.data
bHour BYTE ?					
bMinutes BYTE ?
bSecond BYTE ?.code
main PROC;测试值dx = 01011 101110 10111b  = 11:46:23mov dx, 0101110111010111b;hour 提取小时字段, 把DX高8位移到AL的低位部分,再清除其他无关位mov al, dh							;复制 DHshr al, 3							;右移3位and al, 00011111b				    ;清除位8~位10mov bHour, al						;结果存入变量bHour;minutes 提取小时字段,把DX移到AX中,再清除其他无关位mov ax, dx							;复制 DXshr ax, 5							;右移5 位and al, 00111111b				    ;清除位6~位7mov bMinutes, al				    ;结果存入变量bMinutes;second 提取秒字段 (位0~位4)完全包含在DL寄存器中,将其复制到AL,再清除其他无关位mov al, dl							;复制DLand al, 00011111b				    ;清除位5~位7mov bSecond, al					    ;结果存入变量bSecondINVOKE ExitProcess,0
main ENDP
END main

运行调试:

参考结构:

http://www.dtcms.com/a/272361.html

相关文章:

  • 机器人接入AI的发展前景:从开发者视角看技术融合与生态构建
  • JavaScript中的Screen对象:你的屏幕“身份证”
  • 城市规则管理列表实现逻辑
  • 【Note】Linux Kernel 实时技术深入:详解 PREEMPT_RT 与 Xenomai
  • 【React】MQTT + useEventBus 实现MQTT长连接以及消息分发
  • 昇腾 k8s vnpu配置
  • 在Linux中,如何使用grep awk sed find?
  • 链式二叉树数据结构(递归)
  • 自动化——bat——批量复制所选的文件
  • 微服务架构的演进:迈向云原生——Java技术栈的实践之路
  • SpringBoot整合腾讯云新一代行为验证码
  • RabbitMQ 幂等性
  • Allegro PCB 手动添加元器件全流程解析
  • expect 安装入门手册
  • 【保姆级教程】基于anji-plus-captcha实现行为验证码(滑动拼图+点选文字),前后端完整代码奉上!
  • 人工智能-基础篇-28-模型上下文协议--MCP请求示例(JSON格式,客户端代码,服务端代码等示例)
  • 开源入侵防御系统——CrowdSec
  • Linux 服务器综合性能测试脚本(优化版)结构化分析
  • 若依框架去掉Redis
  • CORESET 0 and SIB1 Scheduling in a Nutshell
  • 论文阅读笔记:VI-Net: Boosting Category-level 6D Object Pose Estimation
  • RocketMQ安装(Windows环境)
  • 上线节点固定,项目进度紧张,如何合理压缩工期
  • NGINX系统基于PHP部署应用
  • 实验作业1+整理笔记截图
  • 实训八——路由器与交换机与网线
  • 栈题解——有效的括号【LeetCode】两种方法
  • 硬件基础------电感
  • Matplotlib-绘制训练曲线指南
  • 力扣刷题记录(c++)06