1. 什么是符号扩展?为什么要用符号扩展?
所谓符号扩展,就是将数据的表示大小加倍,数值仍保持不变,即将符号位扩展到同样大小的寄存器空间中去,由两部分构成一个比原值表示大一倍的数。正数必须要0扩展,负数必须用1扩展。
为什么要进行符号扩展呢?有些指令对操作数位数的要求,例如倍长于除数的被除数,再如将数据位数加长以减少计算过程中的误差。另外,除法运算中规定
除数是BYTE,被除数AX,商存放在AL,余数放在AH
除数是WORD,被除数DX:AX,商存放在AX,余数放在DX
除数是DWORD,被除数EDX:EAX,商存放在EAX,余数放在EDX
被除数总是除数和商的两倍大小,由于余数的符号取决于被除数的符号,计算之后的余数存放在AH,DX,或者EDX中,那么执行除法之前,先将被除数的符号扩展到余数存放寄存器中,这也要求符号扩展。
事实上,在汇编语言里面,有符号数与无符号数计算,很多地方都分成了两套指令,进位与符号操作,都交由程序员来判断和操作,符号扩展正是这一设计思想的体现。
2. 符号扩展指令及应用示例
2.1 CBW(Convert Byte to Word):
规定将AL中的符号扩展到AX中,这是规定死的,执行这个指令就将AL中的符号扩展到AX中了。通过符号扩展,将AL的操作数大小扩大1倍,字节表示的数用字表示,将结果存入AX中。将AL中的符号位第7位,复制到AH中的每一位。例如
XOR EAX,EAX
MOV AL,-128
此时EAX=0x80二进制10000000
CBW
此时EAX=0xFF80进制11111111 10000000符号位1,高8位用符号位1全部填充。
2.2 CWD(Convert Word to Doubleword):
规定将AX中的符号扩展到DX中,执行这个指令就将AX中的符号扩展到DX中了,那为什么不直接将AX中的符号扩展到EAX中,而要那么麻烦的扩展到DX中,再用DX:AX来表示一个有符号数呢,例如,后面的CWDE就是这么干的?估计是在符号扩展指令出来时,还没有32位机,如8086就是16位cpu,也就没有EAX寄存器,只能用两个16位寄存器来表示32位数了,后续32位为了兼容16位时写的程序,也就保留了这种操作方法。另外用于在有符号除法运算中,AX存放商,DX存放余数,不将符号扩展到DX中,IDIV指令是不会将负号自动填进去的,会使用计算结果不正确。
通过符号扩展,将AX中的操作数大小扩大1倍,字表示的数用双字表示,将结果存入DX:AX中,用两个字寄存器存储1个双字的数。例如
XOR EAX,EAX
XOR EDX,EDX
MOV AX,-32768
此时AX=0x8000二进制10000000 00000000
CWD
此时AX=0x8000进制10000000 00000000符号位1,而DX=0xFFFF,即符号位1被填充到了DX。
2.3 CDQ(Convert Doubleword to Quadword):
规定将EAX中的符号扩展到EDX中,执行这个指令就将EAX中的符号扩展到EDX中了。同样,当两个32位有符号数相乘时,符号位就超出了32位表示的范围,像前一样,用两个寄存器EDX:EAX来表示这个乘法的结果,从而得到正确的计算结果。同理,64位出现后,为了兼容32位的程序,也保留了这种操作。同样,在有符号除法运算中,EAX存放商,EDX存放余数,不将符号扩展到EDX中,IDIV指令是不会将负号自动填进去的,会使用计算结果不正确。通过符号扩展,将EAX中的操作数大小扩大1倍,双字表示的数用四字表示,将结果存入EDX:EAX中,用两个双字寄存器存储1个四字的数。例如
XOR EAX,EAX
XOR EDX,EDX
MOV EAX, -2147483648
此时EAX=0x8000 0000二进制10000000 00000000 00000000 00000000
CWD
此时EAX=0x80000000进制10000000 00000000 00000000 000000000符号位1,而EDX=0xFFFFFFFF,即符号位1被填充到了EDX。
2.4 CQO(Convert Quadword to )
规定将RAX中的符号扩展到RDX中去,这是64位cpu模式下使用的指令。用以表示128位的数据,因为两个64位数操乘法运算超过64位的范围,采用以上的表示方法。同样,在有符号除法运算中,RAX存放商,RDX存放余数,不将符号扩展到RDX中,IDIV指令是不会将负号自动填进去的,会使用计算结果不正确。通过符号扩展,将RAX中的操作数大小扩大1倍,四字表示的数用八字表示,将结果存入RDX:RAX中,用两个四字寄存器存储1个八字的数。例如
XOR RAX,RAX
XOR RDX,RDX
MOV RAX, -9223372036854775808
此时RAX=0x8000000000000000二进制
1000000000000000000000000000000000000000000000000000000000000000
CQO
此时RAX=0x8000000000000000 符号位1,而RDX=0xFFFFFFFFFFFFFFFF,即符号位1被填充到了RDX。
2.5 CWDE(Convert Word to Doubleword):
这个E(Extension)应该是指相对于CWD的扩展,是稍后面加的指令,CWD指令出现的时间比CWD早。规定将AX中的符号扩展到EAX中,执行这个指令就将AX中的符号扩展到EAX中了。
CWDE同CWD,只是符号位是直扩展到EAX的高16位中了。
2.6 CDQE(Convert Doubleword to Quadword):
这个E(Extension)应该是指相对于CWQ的扩展,同样,CWD指令出现的时间比CDQE早。
规定将EAX中的符号扩展到RAX中,执行这个指令就将EAX中的符号扩展到RAX中了。
CDQE同CDQ,符号被扩展到RAX的高32位中了。
2.7 MOVZX指令将源操作数的内容复制到目的操作数中,并将该值零扩展至16位或32位。该指令仅适用于无符号整数。
为什么要有这个指令呢,因为MOV指令不能将一个较小的操作数复制到一个较大的操作数。
类为MOV指令要求两个操作数必须一样大。比如将一个较小的操作数在寄存上执行MOV操作,则寄存器的高位没有被覆盖,这个时候我们取整个寄存器值作为MOV的结果是不对的,例如
data1 BYTE 1
data2 WORD ?
XOR EAX,EAX
MOV AX,0FFFFH
MOV AL,data1
MOV data2,AX
这个时候,data2变成了FF01,而不是1。要得到正确的结果,可以有多种方法
比如,先将高位清零
XOR EAX,EAX
MOV AL,data1
MOV data2,AX
这样data2=1了
或者就直接使用这个指令
MOV AX,0FFFFH
MOVZX AX,data1
MOV data2,AX
2.8 MOVSX指令将源操作数的内容复制到目的操作数中,并将该值符号扩展至16位或32位。该指令仅适用于有符号整数。例如
data1 SBYTE -1
data2 SWORD ?
XOR RAX,RAX
MOV AL,data1
MOV data2,AX
这个时候,data2的值为255了,而不是-1,也就是MOV并没有把FF当成有符号数,而是当作值了,那么用MOVSZ就不一样了。
XOR RAX,RAX
MOVSX AX,data1
MOV data2,AX
这个时候data2=-1,是正确的结果。