汇编基础
整数运算
本章将学习汇编语言的最大优势之一:基本二进制位移和循环位移技术。实际上,位操作是计算机图形学,数据加密和硬件控制的固有部分。
位移动(bit shifting)
移动操作数的位有两种方法,
第一种: 逻辑位移(logic shift),空出的位用0填充
第二种:算数位移(arithmetic shift),空出的位用原数据的符号位填充
符号 | 说明 |
---|---|
SHL | 左移 |
SHR | 右移 |
SAL | 算数左移 |
SAR | 算数右移 |
- 位移操作的最高位或最低位都会被复制到CF(进位标志位)
- 当一个数多次右移时,进位标志位保存的是最后移出最低有效位的数值
- 最高有效位(MSB),最低有效位(LSB)
例子:
有符号除法
1 | mov dl, -128 ;DL=10000000b |
即用SAR实现有符号数除以2的整数次幂
AX符号扩展到EAX
1 | mov ax, -128 ;EAX=????FF80h |
先左移16位然后算数右移16位就完成了将AX符号扩展到EAX
位元循环(Bitwise Rotation)
以循环的方式来移位,不会弃位,从数的一段循环出去的位会在该数的另一端出现
符号 | 说明 |
---|---|
ROL | 循环左移 |
ROR | 循环右移 |
RCL | 带进位的循环左移 |
RCR | 带进位的循环右移 |
- ROL和ROR同样最高位和最低位会被复制到CF中
- RCL和RCR会将最高位或最低位移动到CF中,CF移动到最低位或最高位
例子:
循环多次
1 | mov al, 00100000b |
位组交换
1 | mov al, 26h |
利用ROL可以交换一个字节的高四位和低四位
从进位标志位恢复位
1 | .data |
有符号数溢出
如果有符号数循环移动1位生成的结果超过了目的操作数的有符号数范围,就发生了溢出
1 | mov al, +127 |
如果循环次数大于1,则溢出标志位无定义
SHLD/SHRD
SHLD(双精度左移)指令将目的操作数向左移动指定位数,形成的空位由源操作数的高位填充,最高位移动到CF。同理SHRD。
Q:编写一组指令不使用SHRD将AX的最低位移入BX的最高位
A:
1 | ror ax, 1 |
K:即借用CF作为中转站,我们若想对数组进行移位操作,思路和该方法一样
Q:计算EAX中32位数奇偶性的方法之一是利用循环把该数的每一位都移动进入进位标志位,然后计算进位标志位置1的次数,根据结果设置奇偶标志位
A:
1 | .386 |
其实后面的代码意义不大,目的在于置奇偶标志位,若esi为偶数,则最低位一定是0,反之为奇数
乘除指令
Q:请说明执行MUL指令和单操作数的IMUL指令时,不会发生溢出的原因
A:MUL指令(无符号数乘法)中的单操作数是乘数,如下表列出了默认的被乘数和乘积。由于目的操作数是被乘数和乘数大小的2倍,所以不会溢出
被乘数 | 乘数 | 乘积 |
---|---|---|
AL | reg/mem8 | AX |
AX | reg/mem16 | DX:AX |
EAX | reg/mem32 | EDX:EAX |
Q:生成乘积时,单操作数IMUL和MUL指令有何不同?
A:IMUL(有符号数乘法)执行有符号的整数乘法,与MUL不同的是IMUL会保留乘积的符号
Q:什么情况下单操作数IMUL会将进位标志位和溢出标志位置1?
A:与MUL指令一样,其乘积的存储大小使其不会发生溢出。同时,如果乘积的高半部分不是其低半部分的符号扩展,则进位标志位和溢出标志位置1
- 单操作数下,将乘积存放在AX,DX:AX,EDX:EAX中
- 双操作数下第一个操作数必须是寄存器,第二个可以是寄存器,内存操作数,立即数。会将结果放在第一个寄存器中
- 三操作数下第一个是结果保存的寄存器,第二个是reg/mem,第三个是imm,表示将立即数和寄存器/内存数相乘放入第一个寄存器中
注意:二三个操作数时若寄存器大小不够,会按大小低位截取,并将溢出和进位置1
Q:DIV指令中,若EBX为操作数,商保存在哪个寄存器?BX为操作数呢?
A:
被除数 | 除数 | 商 | 余数 |
---|---|---|---|
AX | reg/mem8 | AL | AH |
DX:AX | reg/mem16 | AX | DX |
EDX:EAX | reg/mem32 | EAX | EDX |
Q:有符号除法和无符号的区别是?
A:几乎完全相同,只有一个重要区别,在执行除法前,必须对被除数进行符号扩展(将一个数的最高位复制到包含该数的变量或寄存器的所有高位中)
K:3种符号扩展指令CWD(字转双字),CBW(字节转字),CDQ(双字转四字)
例子:
1 | .data |
结果并不是我们想要的,因为我们的DX和AX组合时忽略了符号问题
加上cwd
即可,此时DX:AX=FFFFFF9Bh
- 关于CWD的更多说明(2019.11.14修改)
1 | CBW AL -> AX |
其主要作用之一就是将我们的数据进行符号扩展或截断,就想题目中一样,我们想要用dx:ax做除法的除数,但我们的数仅仅有16位,对dx清零后直接用dx和ax组合会有符号的问题,这时候进行一下符号的扩展就能解决符号的问题
扩展加减法
扩展精度加减法(extended precision addition and subtraction)是对基本没有大小限制的数进行加减法。比如在C++中,没有标准运算符会允许两个1024位整数相加,但在汇编中,ADC(带进位加法),SBB(带借位减法)就适合这样的操作。
例子:两组数的相加
1 | INCLUDE Irvine32.inc |
- 注意我们的核心过程ExtendedAdd,每次循环使用adc并压栈进位标志位,为的是后面的add不影响我们的进位,循环前恢复就可以在下次做adc运算是加上了
- 最后一步是检查操作数的最高位是否产生了进位,有进位就放入sum的最高位
ASCII和非压缩10进制
BCD码
用二进制编码的十进制数,通过专门的十进制数运算指令进行处理。计算机中可有专门的逻辑线路支持BCD码运算
压缩BCD
用4位二进制数表示1位十进制数,如:( 59 )10 =( 0101 1001 )BCD
非压缩BCD
用8位二进制数表示1位十进制数,如:( 59 )10 =( 0000 0101 0000 1001 )BCD
数字的 ASCII 码是一种 非压缩的 BCD 码
十进制调整指令
对于两个十进制数求和:
1.我们可以都转成2进制然后求和再转回10进制
2.直接进行字符串加法,再进行调整
相较于第二种方法,第一种方法过于麻烦,我们不会使用
而第二种方法的误差是统一的:
所以可以设计指令来修正这个误差,当然也可以手动修正
非压缩的BCD码调整指令
命令 | 说明 |
---|---|
AAA | 执行加法后调整 |
AAS | 执行减法后调整 |
AAM | 执行乘法后调整 |
AAD | 执行除法后调整 |
非压缩十进制数的最高位为0000b,而ASCII十进制数的最高位为0011b,任何情况下这两种类型的数字占一个字节
ASCII运算要比二进制运算慢的多,但在处理实数时不会出现浮点数运算的舍入误差的危险
1 | mov ah, 0 ;记得清零 |
我们发现经过调整,2+8得到了10,而不是16进制相加的结果。而or ax, 3030h
把10转变为了字符串’10’
压缩BCD码调整指令
仅用于32位模式
压缩十进制数的每个字节存放两个十进制数,每个数字用4位表示,如果数字个数为奇数,最高的半字节用0填充(有点像8421码的编码理念)
压缩十进制数的优势在于:
1.数据可以几乎包含含有任意数字的有效数字
2.与ASCII码的转换相对简单
DAA,DAS分别为调整压缩十进制数的加法和减法,我们会在练习题中使用它们解决问题
写在后面
果然返校后效率激增
充分证明在家里的学习环境没有学校好,或者是我太咕了
总共13章,加把劲在开学前看完