Assembly 正负号延伸
在汇编语言中,所有数据都有一个指定的大小。为了与其它数据一起使用而改变数据大小是不常用的。减小它的大小是最简单的。
减小数据的大小
要减小数据的大小,只需要简单地将多余的有效位移位即可。这是一个普通的例子:
mov ax, 0034h ; ax = 52 (以十六位储存)
mov cl, al ; cl = ax的低八位
当然,如果数字不能以更小的大小来正确描述,那么减小数据的大小将不能工作。例如,如果AX是0134h (或十进制的308) ,那么上面的代码仍然将CL置为34h。这种方法对于有符号和无符号数都能工作。考虑有符号数:如果AX是FFFFh (也就是¡1),那么CL 将会是FFh (一个字节表示的¡1)。然而,注意如果在AX里的值是无符号的,这个就不正确了!
无符号数的规则是:为了能转换正确,所有需要移除的位都必须是0。有符号数的规则是:需要移除的位必须要么都是1,要么都是0。另外,没有移除的第一个比特位的值必须等于移除的位的第一位。这一位将会是变小的值的新的符号位。这一位与原始符号位相同是非常重要的!
增大数据的大小
增大数据的大小比减小数据的大小更复杂。考虑十六进制字节:FF。如果它扩展成一个字,那么这个字的值应该是多少呢?它取决于如何解释FF。如果FF是一个无符号字节(十进制中为),那么这个字就应该是00FF;但是,如果它是一个有符号字节(十进制中为¡1),那么这个字就应该为FFFF。
一般说来,扩展一个无符号数,你需将所有的新位置为0.因此,FF就变成了00FF。但是,扩展一个有符号数,你必须扩展符号位。这就意味着所有的新位通过复制符号位得到。因为FF的符号位为1,所以新的位必须全为1,从而得到FFFF。如果有符号数5A (十进制中为90)被扩展了,那么结
果应该是005A。
果应该是005A。
80386提供了好几条指令用于数的扩展。谨记电脑是不清楚一个数是有符号的或是无符号的。这取决于程序员是否用了正确的指令。对于无符号数,你可以使用MOV指令简单地将高位置0。例如,将一个在AL中的无符号字节扩展到AX中:
mov ah, 0 ; 输出高8位为0
但是,使用MOV指令把一个在AX中的无符号字转换成在EAX中的无符号双字是不可能的。为什么不可以呢?因为在MOV指令中没有方法指定EAX的高16位。80386通过提供一个新的指令MOVZX来解决这个问题。这个指令有两个操作数。目的操作数(第一个操作数)必须是一个16或32位的寄存器。源
操作数(第二个操作数)可以是一个8或16位的寄存器或内存中的一个字。另一个限制是目的操作数必须大于源操作数。(许多指令要求源和目的操作数必须是一样的大小。) 这儿有几个例子:
操作数(第二个操作数)可以是一个8或16位的寄存器或内存中的一个字。另一个限制是目的操作数必须大于源操作数。(许多指令要求源和目的操作数必须是一样的大小。) 这儿有几个例子:
对于有符号数,在任何情况下,没有一个简单的方法来使用MOV 指令。8086提供了几条用来扩展有符号数的指令。CBW (Convert Byte toWord(字节转换成字))指令将AL正负号扩展成AX。操作数是不显示的。CWD(ConvertWord to Double word(字转换成双字))指令将AX正负号扩展成DX:AX。
DX:AX表示法表示将DX和AX寄存器当作一个32位寄存器来看待,其中高16位在DX中,低16位在AX中。(记住8086没有32位寄存器!) 80386加了好几条新的指令。CWDE (Convert Word to Double word Extended(字转换成扩展的双字))指令将AX正负号扩展成EAX。CDQ (Convert Double word toQuad word(双字扩展成四字))指令将EAX正负号扩展成EDX:EAX (64位!).最后,MOVSX 指令像MOVZX指令一样工作,除了它使用有符号数的规则外。
C编程中的应用
无符号和有符号数的扩展同样发生在C中。C中的变量可以被声明成有符号的,或无符号的(int是有符号的)。考虑在图2.1中的代码。在第3行中,变量a使用了无符号数的规则(使用MOVZX)进行了扩展,但是在第4行,变量b使用了有符号数的规则(使用MOVSX)进行了扩展。
这有一个直接与这个主题相关的普遍的C编程中的一个bug。考虑在图2.2中的代码。fgetc()的原型是:
int fgetc( FILE * );
一个可能的问题:为什么这个函数返回一个int类型,然后又被当作字符类型被读呢?原因是它一般确实是返回一个char 类型的值(使用0扩展成一个int类型的值)。但是,有一个值它可能不会以字符的形式返回:EOF。这是一个宏,通常被定义为¡1。因此,fgetc()不是返回一个通过扩展成int类型得到的char类型的值(在十六进制中表示为000000xx),就是EOF(在十六进制中表示为FFFFFFFF)。
图2.2中的程序的基本的问题是fgetc()返回一个int类型,但是这个值以char的形式储存。C将会切去较高顺序的位来使int类型的值适合char类型。唯一的问题是数(十六进制) 000000FF和FFFFFFFF都会被切成字节FF。因此,while循环不能区别从文件中读到的字节FF和文件的结束。
实际上,在这种情况下代码会怎么做,取决于char是有符号的,还是无符号的。为什么?因为在第2行ch是与EOF进行比较。因为EOF是一个int类型的值,ch将会扩展成一个int类型,以便于这两个值在相同大小下比较。就像图2.1展示的一样,变量是有符号的还是无符号的是非常重要的。
如果char是无符号的,那么FF就被扩展成000000FF。这个拿去与EOF
(FFFFFFFF)比较,它们并不相等。因此,循环不会结束。
(FFFFFFFF)比较,它们并不相等。因此,循环不会结束。
如果char是有符号的,FF就被扩展成FFFFFFFF。这就导致比较相等,循环结束。但是,因为字节FF可能是从文件中读到的,循环就可能过早地被结束了。
这个问题的解决办法是定义ch 变量为int类型,而不是char类型。当做了这个改变,在第2行就不会有切去和扩展操作执行了。在循环休内,对值进行切去操作是很安全的,因为ch在这儿必须实际上已经是一个简单的字节了。