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

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

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

7.5 ASCI和非压缩十进制运算

(7.5节讨论的指令只能用于32位模式编程。)到目前为止,本书讨论的整数运算处理的都是二进制数。虽然CPU用二进制运算,但是也可以执行ASCI十进制串的运算。使用后者进行运算,对用户而言既便于输入也便于在控制台窗口显示,因为不用进行二进制转换假设程序需要用户输人两个数,并将它们相加。若用户输人3402和1256,则程序输出如下所示:

输入第一个数:3402

输入第二个数:1256

和 数: 4658

有两种方法可以计算并显示和数:

1)将两个操作数都转换为二进制,进行二进制加法,再将和数从二进制转换为ASCII数字串。

2)直接进行数字串的加法,按序相加每对ASCI数字(2+6、0+5、4+2、3+1)。和数为ASCII数字串,因此可以直接显示在屏幕上。

第二种方法需要在执行每对ASCI数字相加后,用特殊指令来调整和数。有四类指令用于处理 ASCII加法、减法、乘法和除法,如下所示:

AAA

(执行加法后进行 ASCI 调整)

AAM

(执行乘法后进行 ASCI 调整)

AAS

(执行减法后进行 ASCII 调整)

AAD

(执行除法前进行 ASCII 调整)

ASCII 十进制数和非压缩十进制数 非压缩十进制整数的高4位总是为零,而ASCII十进制数的高4位则应该等于0011b。在任何情况下,这两种类型的每个数字都占用一个字节。下面的例子展示了3402用这两种类型存放的格式:

尽管ASCI运算执行速度比二进制运算要慢很多,但是它有两个明显的优点:

●不必在执行运算之前转换串格式。

●使用假设的十进制小数点,使得实数操作不会出现浮点运算的舍入误差的危险。

ASCII 加减法运行操作数为ASCI格式或非压缩十进制格式,但是乘除法只能使用非压缩十进制数。

7.5.1 AAA 指令

在 32位模式下,AAA(加法后的ASCI调整)指令调整ADD或ADC指令的二进制运算结果。设两个ASCI数字相加,其二进制结果存放在AL中,则AAA将AL转换为两个非压缩十进制数字存人AH和AL。一旦成为非压缩格式,通过将AH和AL与30h进OR运算,很容易就能把它们转换为 ASCII码。

下例展示了如何用AAA指令正确地实现ASCI数字8加2。在执行加法之前,必须把AH清零,否则它将影响AAA执行的结果。最后一条指令将AH和AL转换为ASCI数字:

mov ah, 0
mov al, '8'							;AX = 0038h
add al, '2'							;AX = 006Ah
aaa									;AX = 0100h(结果进行ASCII调整)
or ax, 3030h						;AX = 3130h ='10'(转换为ASCII码)

使用 AAA 实现多字节加法

现在来查看一个过程,其功能为实现包含了隐含小数点的ASCII十进制数值相加。由于每次数字相加的进位标志位都要传递到更高位,因此,过程的实现要比想象的更复杂一些。下面的伪代码中,acc代表的是一个8位的累加寄存器:

esi (index)=length of first number - 1
edi (index)=length of first number
ecx =lengthoffirst number
set carry value to 0
Loopacc = first number[esi]add previous carry to accsave carry in carrylacc += second_number[esi]OR the carry with carry1sum[edi] = accdec edi
Until ecx == 0
Store last carry digit in sum

进位值必须总是被转换为ASCI码。将进位值与第一个操作数相加时,就需要用AAA来调整结果。程序清单如下:

;ASCII_add.asm   ASCII加法
;对有隐含固定小数点的串执行ASCII运算。INCLUDE Irvine32.incDECIMAL_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 - 1mov edi, SIZEOF decimal_onemov ecx, SIZEOF decimal_onemov bh, 0										;进位值清零
L1:	mov ah, 0											;执行加法前清除AHmov al, decimal_one[esi]						;取第一个数字add al, bh										;加上之前的进位值aaa												;调整和数AH=进位值mov bh, ah										;将进位保存到carry1or bh, 30h										;将其转换为ASCII码add al, decimal_two[esi]						;加第二个数字aaa												;调整和数AH=进位值or bh, ah										;进位值与 carry1进行 OR运算or bh, 30h										;将其转换为ASCII 码or al, 30h										;将AL转换为ASCII码mov sum[edi], al								;将AL保存到sumdec esi											;后退一个数字dec edi												loop L1mov sum[edi], bh								;保存最后的进位值;显示和数字符串。mov edx, OFFSET sumcall WriteStringcall CrlfINVOKE ExitProcess,0
main ENDP
END main

程序输出如下所示,和数没有显示十进制小数点:

7.5.2 AAS 指令

32位模式下,AAS(减法后的ASCII调整)指令紧随SUB或SBB指令之后,这两条指令执行两个非压缩十进制数的减法,并将结果保存到AL中。AAS指令将AL转换为ASCII码的数字形式。只有减法结果为负时,调整才是必需的。比如,下面的语句实现ASCI码数字8减去9:

;7.5.2.asm  7.5.2 AAS 指令
;下面的语句实现ASCI码数字8减去9:.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD.data
val1 BYTE '8'
val2 BYTE '9'.code
main PROCmov ah, 0mov al, val1					;AX = 0038hsub  al, val2					;AX = 00FFhaas								;AX = 0FF09hpushf							;保存进位标志位or al, 30h						;AX = 0FF39hpopf							;恢复进位标志位INVOKE ExitProcess,0
main ENDP
END main

执行SUB指令后,AX等于00FFh。AAS指令将AL转换为09h,AH减1等于FFh并且把进位标志位置1。

7.5.3 AAM 指令

32位模式下,MUL执行非压缩十进制乘法,AAM(乘法后的ASCII调整)指令转换由其产生的二进制乘积。乘法只能使用非压缩十进制数。下面的例子实现5乘以6,并调整AX中的结果。调整后,AX=0300h,非压缩十进制表示为30:

;7.5.3.asm  7.5.3 AAM指令
;下面的例子实现5乘以6,并调整AX中的结果。
;调整后,AX=0300h,非压缩十进制表示为30:.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD.data
ascVal BYTE 05h, 06h.code
main PROCmov bl, ascVal						;第1个操作数mov al, [ascVal+1]					;第2个操作数mul bl								;AX=001Eh aam									;AX=0300h INVOKE ExitProcess,0
main ENDP
END main

7.5.4 AAD 指令

32位模式下,AAD(除法之前的ASCII调整)指令将AX中的非压缩十进制被除数转换为二进制,为执行DIV指令做准备。下面的例子把非压缩0307h转换为二进制数,然后除以5。DIV指令在AL中生成商07h,在AH中生成余数02h:

;7.5.4.asm  7.5.4 AAD指令
;下面的例子把非压缩0307h转换为二进制数,然后除以5。
;DIV指令在AL中生成商07h,在AH中生成余数02h:.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD.data
quotient BYTE ?
remainder BYTE ?.code
main PROCmov ax, 0307h					;被除数aad								;AX = 0025hmov bl, 5						;除数div bl							;AX=0207hmov quotient, almov remainder, ahINVOKE ExitProcess,0
main ENDP
END main

7.5.5 本节回顾

1.编写一条指令,将 AX中的一个两位非压缩十进制整数转换为十进制的 ASCII码。

答:or ax, 3030h

2.编写一条指令,将 AX中的一个两位 ASCII码十进制整数转换为非压缩十进制形式

答:and ax, 0F0Fh

3.编写有两条指令的序列,将 AX中的一个两位 ASCII 码十进制整数转换为二进制。

答:and ax, 0F0Fh ;转换为非压缩形式

aad

4.编写一条指令,将 AX中的一个无符号二进制整数转换为非压缩十进制数。

答:aam

7.6 压缩十进制运算

(7.6节讨论的指令仅用于32位编程模式。)压缩十进制数的每个字节存放两个十进制数字,每个数字用4位表示。如果数字个数为奇数,则最高的半字节用零填充。存储大小可变:

bcd1 QWORD 2345673928737285h		;十进制数 2345673928737285
bcd2 DWORD 12345678h				;十进制数12345678
bcd3 DWORD 08723654h				;十进制数8723654
bcd4 WORD 9345h						;十进制数9345
bcd5 WORD 0237h						;十进制数237
bcd6 BYTE 34h						;十进制数34

压缩十进制存储至少有两个优势:

●数据几乎可以包含任何个数的有效数字。这使得以很高的精度执行计算成为可能

●实现压缩十进制数与 ASCII码之间的相互转换相对简单。

DAA(加法后的十进制调整)和DAS(减法后的十进制调整)这两条指令调整压缩十进制数加减法的结果。可惜的是,目前还没有与乘除法有关的相似指令。在这些情况下,相乘或相除的数必须是非压缩的,执行后再压缩。

7.6.1 DAA 指令

32位模式下,ADD或ADC指令在AL中生成二进制和数,DAA(加法后的十进制调整)指令将和数转换为压缩十进制格式。比如,下述指令执行压缩十进制数35加48。二进制和数(7Dh)被调整为83h,即35和48的压缩进制和数。

mov al, 35h			
add al, 48h			;AL=7Dh
daa					;AL=83h(调整后的结果)

DAA的内部逻辑请参阅Intel指令集参考手册。示例 下面的程序执行两个16位压缩十进制整数加法,并将和数保存在一个压缩双字中。加法要求和数变量的存储大小比操作数多一个数字:

;AddPacked.asm   7.6.1  DAA指令   压缩十进制示例
;下面的程序执行两个16位压缩十进制整数加法,并将和数保存在一个压缩双字中。
;加法要求和数变量的存储大小比操作数多一个数字:INCLUDE Irvine32.inc.data
packed_1 WORD 4536h
packed_2 WORD 7207h
sum DWORD ?.code
main PROC;初始化和数与索引:mov sum, 0mov esi, 0;低字节相加。mov al, BYTE PTR packed_1[esi]add  al, BYTE PTR packed_2[esi]daamov BYTE PTR sum[esi], al;高字节相加,包括进位标志位。inc esimov al, BYTE PTR packed_1[esi]adc  al, BYTE PTR packed_2[esi]daamov BYTE PTR sum[esi], al;若还有进位,则加上该进位值。inc esimov al, 0adc  al, 0mov BYTE PTR sum[esi], al;用十六进制显示和数,mov eax, sumcall WriteHexcall Crlfexit;INVOKE ExitProcess,0
main ENDP
END main

显然,这个程序包含重复代码,因此建议使用循环结构。本章的一道习题将会要求编写一个过程,实现任意大小的压缩十进制整数加法。

7.6.2 DAS指令

32位模式下,SUB或SBB指令在AL中生成二进制结果,DAS(减法后的十进制调整)指令将其转换为压缩十进制格式。比如,下面的语句计算压缩十进制数85减48,并调整结果:

mov bl, 48h							
mov al, 85h							
sub al, bl						;AL = 3Dh
das								;AL = 37h (调整后)

DAS的内部逻辑请参阅Intel指令集参考手册.

7.6.3 本节回顾

1.举例说明,什么情况下DAA指令会把进位标志位置1?

答:当压缩十进制加法的和数大于99时,DAA将进位标志位置1,例如:

;7.6.3_1.asm  7.6.3   本节回顾
;1.举例说明,什么情况下DAA指令会把进位标志位置1?.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD.code
main PROCmov al, 56hadd  al, 92h				;AL = E8hdaa							;AL = 48h, CF = 1INVOKE ExitProcess,0
main ENDP
END main

2.举例说明,什么情况下DAS指令会把进位标志位置1?

答:若从小的压缩十进制整数中减去大的压缩十进制整数,则DAS将进位标志位置1.例如:

;7.6.3_2.asm  7.6.3   本节回顾
;2.举例说明,什么情况下DAS指令会把进位标志位置1?.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD.code
main PROCmov al, 56hsub  al, 92h				;AL = C4hdas							;AL = 64h, CF = 1INVOKE ExitProcess,0
main ENDP
END main

3.两个长度为"字节的压缩十进制整数相加时,和数应该保留多少字节?

答:和数应该保留n+1个字节。

7.7 本章小结

与前面章节介绍的位元指令一样,移位指令也是汇编语言最显著的特点之一。一个数移位就意味着把它的位元进行右移或左移。

SHL(左移)指令把目标操作数的每一位都向左移动,最低位用0填充。SHL最大的作用之一是快速实现与2的幂相乘。任何操作数左移位即为乘以2"。SHR(右移)指令则把每一位都向右移动,最高位用0填充。任何操作数右移位即为除以2"。

SAL(算术左移)和SAR(算术右移)是特别为有符号数移位设计的指令。

ROL(循环左移)指令把每一位向左移动,并将最高位复制到进位标志位和最低位。ROR(循环右移)指令把每一位向右移动,并将最低位复制到进位标志位和最高位。

RCL(带进位循环左移)指令把每一位都左移,并先将进位标志位复制到移位结果的最低位,再将最高位复制到进位标志位。RCR(带进位循环右移)指令把每一位都右移,并将最低位复制到进位标志位,而进位标志位则复制到结果的最高位。

x86处理器可使用的SHLD(双精度左移)和SHRD(双精度右移)指令对大数的移位非常有用。

32位模式下,MUL指令实现一个8位、16位或32位的操作数与AL、AX或EAX相乘64位模式下,一个数还可以实现与RAX寄存器相乘。IMUL指令执行有符号数乘法,它有三种格式:单操作数、双操作数和三操作数。

32位模式下,DIV指令实现8位、16位或32位操作数的除法。64位模式下,还可以实现 64位除法。IDIV指令执行有符号数乘法,其格式与DIV指令相同。

CBW(字节转字)指令把AL的符号位扩展到AH寄存器。CDO(双字转四字)指令把EAX的符号位扩展到EDX寄存器。CWD(字转双字)指令把AX的符号位扩展到DX寄存器。

扩展加减法是指加减任意大小的数,ADC和SBB指令可以用于实现这种加减运算ADC(带进位加法)指令实现源操作数与进位标志位的内容和目的操作数相加。SBB(带借位减法)指令实现目的操作数减去源操作数和进位标志位的值。

ASCII十进制数每个字节存放一个数字,并编码为ASCI形式。AAA(加法后的ASCII调整)指令将ADD或ADC指令的二进制结果转换为ASCII十进制。AAS(减法后的ASCII调整)指令将SUB或SBB指令的二进制结果转换为ASCII十进制。所有这些指令都只能用于32位模式。

非压缩十进制数每个字节存放一个十进制数字,表现为二进制数值。AAM(乘法后的ASCII 调整)指令转换的是MUL指令执行非压缩十进制数乘法所生成的二进制结果。AAD(除法前的 ASCI 调整)指令在执行 DIV指令之前,将非压缩十进制被除数转换为二进制。所有这些指令都只能用于32位模式。

压缩十进制数每个字节存放两个十进制数字。DAA(加法后的十进制调整)指令转换的是 ADD或 ADC指令执行压缩十进制加法所生成的二进制结果。DAS(减法后的十进制调整)指令转换的是SUB或SBB指令执行压缩十进制减法所生成的二进制结果。所有这些指令都只能用于 32 位模式,

7.8 关键术语

7.8.1 术语

7.8.2 指令、运算符和伪指令


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/88637.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应用笔记|数字化仪在医学SS-OCT中的应用

引言近些年来,OCT(光学相干断层扫描,Optical Coherence Tomography)作为一种非破坏性3D光学成像技术逐渐在医学眼科设备中流行起来。OCT可提供实时一维深度或二维截面或三维立体的图像,分辨率可达微米(μm&…

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议 在2025年的技术环境下,Ubuntu 22.04和24.04 LTS各有优势,选择哪一个取决于具体应用场景和用户需求。经过对系统内核、桌面环境、软件生态、生命周期支持等多方面因素的综合分析,本报告将…

Linux进程的生命周期:状态定义、转换与特殊场景

前言 在Linux系统中,进程是资源分配和调度的基本单位,而进程状态则是理解进程行为的关键。从运行中的任务(TASK_RUNNING)到僵尸进程(EXIT_ZOMBIE),每个状态都反映了进程在内核调度、资源等待或父…

神经网络简介

大脑的基本计算单位是神经元(neuron)。人类的神经系统中大约有860亿个神经元,它们被大约10^14-10^15个突触(synapses)连接起来。下面图表的左边展示了一个生物学的神经元,右边展示了一个常用的数学模型。每…

多路由协议融合与网络服务配置实验(电视机实验)

多路由协议融合与网络服务配置实验文档 一、实验用途和意义 (一)用途 本实验模拟企业复杂网络环境,整合 OSPF、RIPv2 动态路由协议,结合 DHCP、FTP、Telnet 服务配置及访问控制策略,实现多区域网络互联、服务部署与…

在指定conda 环境里安装 jupyter 和 python kernel的方法

在 Conda 的指定环境中安装 Jupyter 和 Python Kernel 是一个常见操作,以下是详细步骤,确保在指定环境中正确配置 Jupyter 和 Python Kernel: 1. 准备工作 确保已安装 Anaconda 或 Miniconda,Conda 环境管理工具可用。确认已创建或计划使用的 Conda 环境。2. 步骤:安装 J…

【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)

🔥个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题 🍉学习方向:C/C方向 ⭐️人生格言:为天地立心,为生民立命,为…

Java+AI精准广告革命:实时推送系统实战指南

⚡ 广告推送的世纪难题 用户反感&#xff1a;72%用户因无关广告卸载APP 转化率低&#xff1a;传统推送转化率<0.5% 资源浪费&#xff1a;40%广告预算被无效曝光消耗 &#x1f9e0; 智能广告系统架构 &#x1f525; 核心模块实现&#xff08;Java 17&#xff09; 1. 实时…

JVM组成及运行流程 - 面试笔记

JVM整体架构 JVM&#xff08;Java Virtual Machine&#xff09;是Java程序运行的核心环境&#xff0c;主要由以下几个部分组成&#xff1a;1. 程序计数器&#xff08;Program Counter&#xff09; 特点&#xff1a;线程私有&#xff0c;每个线程都有独立的程序计数器作用&#…

JavaEE——线程池

目录前言1. 概念2. 线程池相关参数3. Executors的使用总结前言 线程是为了解决进程太重的问题&#xff0c;操作系统中进程的创建和销毁需要较多的系统资源&#xff0c;用了轻量级的线程来代替部分线程&#xff0c;但是如果线程创建和销毁的频率也开始提升到了一定程度&#xf…

3 c++提高——STL常用容器(一)

目录 1 string容器 1.1 string基本概念 1.2 string构造函数 1.3 string赋值操作 1.4 string字符串拼接 1.5 string查找和替换 1.6 string字符串比较 1.7 string字符存取 1.8 string插入和删除 1.9 string子串 2 vector容器 2.1 vector基本概念 2.2 vector构造函数…

手把手教你用【Go】语言调用DeepSeek大模型

1、首先呢&#xff0c;点击 “DeepSeek”” 这个&#xff0c; 可以充1块玩玩。 2、然后获取api-key 3、替换apiKey const (apiURL "https://api.deepseek.com/v1/chat/completions"apiKey "your api key" // 替换为你的实际 API KeymodelName &…

自动化UI测试工具TestComplete的核心功能及应用

对桌面应用稳定性与用户体验的挑战&#xff0c;手动测试效率低、覆盖有限&#xff0c;而普通自动化工具常难以应对复杂控件识别、脚本灵活性和大规模并行测试的需求。 自动化UI测试工具TestComplete凭借卓越的对象识别能力、灵活的测试创建方式以及高效的跨平台并行执行功能&a…

【C/C++】迈出编译第一步——预处理

【C/C】迈出编译第一步——预处理 在C/C编译流程中&#xff0c;预处理&#xff08;Preprocessing&#xff09;是第一个也是至关重要的阶段。它负责对源代码进行初步的文本替换与组织&#xff0c;使得编译器在后续阶段能正确地处理规范化的代码。预处理过程不仅影响编译效率&…

快捷键——VsCode

一键折叠所有的代码块 先按 ctrl K&#xff0c;再ctrl 0 快速注释一行 ctrl /

import 和require的区别

概念 import 是es6 规范&#xff0c;主要应用于浏览器和主流前端框架当中&#xff0c;export 导出&#xff0c; require 是 commonjs 规范&#xff0c;主要应用于nodejs环境中&#xff0c;module.exports 导出编译规则 import 静态导入是编译时解析&#xff0c;动态导入是执…

8、鸿蒙Harmony Next开发:相对布局 (RelativeContainer)

目录 概述 基本概念 设置依赖关系 设置参考边界 设置锚点 设置相对于锚点的对齐位置 子组件位置偏移 多种组件的对齐布局 组件尺寸 多个组件形成链 概述 RelativeContainer是一种采用相对布局的容器&#xff0c;支持容器内部的子元素设置相对位置关系&#xff0c;适…

Linux命令的命令历史

Linux下history命令可以对当前系统中执行过的所有shell命令进行显示。重复执行命令历史中的某个命令&#xff0c;使用&#xff1a;!命令编号&#xff1b;环境变量histsize的值保存历史命令记录的总行数&#xff1b;可用echo查看一下&#xff1b;需要大写&#xff1b;环境变量hi…

【C++小白逆袭】内存管理从崩溃到精通的秘籍

目录【C小白逆袭】内存管理从崩溃到精通的秘籍前言&#xff1a;为什么内存管理让我掉了N根头发&#xff1f;内存四区大揭秘&#xff1a;你的变量都住在哪里&#xff1f;&#x1f3e0;内存就像大学宿舍区 &#x1f3d8;️C语言的内存管理&#xff1a;手动搬砖时代 &#x1f9f1;…

【网络安全】利用 Cookie Sandwich 窃取 HttpOnly Cookie

未经许可,不得转载。 文章目录 引言Cookie 三明治原理解析Apache Tomcat 行为Python 框架行为窃取 HttpOnly 的 PHPSESSID Cookie第一步:识别 XSS 漏洞第二步:发现反射型 Cookie 参数第三步:通过 Cookie 降级实现信息泄露第四步:整合攻击流程修复建议引言 本文将介绍一种…