汇编中有符号与无符号数以及CF,OF标志位的区分
一、 只有一个标准!
首先需要知道,计算机对数值的存储采用补码形式存储,一来避免了+0和-0的尴尬,二来数值的加法和减法可以统一为补码的加法。
在汇编语言层面,定义变量的时候,没有 signed和unsignde 之分,汇编器统统将你输入的整数字面量当作有符号数(最高位的符号位根据输入的数值符号决定)处理成二进制补码存入到计算机中,只有这一个标准!
汇编器不会区分有符号还是无符号然后用两个标准来处理,它统统当作有符号的!并且统统汇编成补码!也就是说,db -20 汇编后为:EC ,而 db 236 汇编后也为 EC 。这里有一个小问题,思考深入的朋友会发现,db 是分配一个字节,那么一个字节能表示的有符号整数范围是:-128 ~ +127 ,那么 db 236 超过了这一范围,怎么可以?是的,+236 的补码的确超出了一个字节的表示范围,那么拿两个字节(当然更多的字节更好了)是可以装下的,应为:00 EC,也就是说 +236的补码应该是00 EC,一个字节装不下,但是,别忘了“截断”这个概念,就是说最后的结果被截断了,00 EC 是两个字节,被截断成 EC ,所以,这是个“美丽的错误”,为什么这么说?因为,当你把 236 当作无符号数时,它汇编后的结果正好也是 EC ,这下皆大欢喜了,虽然汇编器只用一个标准来处理,但是借用了“截断”这个美丽的错误后,得到的结果是符合两个标准的!也就是说,给你一个字节,你想输入有符号的数,比如 -20 那么汇编后的结果是正确的;如果你输入 236 那么你肯定当作无符号数来处理了(因为236不在一个字节能表示的有符号数的范围内啊),得到的结果也是正确的。于是给大家一个错觉:汇编器有两套标准,会区分有符号和无符号,然后分别汇编。其实,你们被骗了。:-)
二、存在两套指令!
第一点说明汇编器只用一个方法把整数字面量汇编成真正的机器数。但并不是说计算机不区分有符号数和无符号数,相反,计算机对有符号和无符号数区分的十分清晰,因为计算机进行某些同样功能的处理时有两套指令作为后备,这就是分别为有符号和无符号数准备的。但是,这里要强调一点,一个数到底是有符号数还是无符号数,计算机并不知道,这是由你来决定的,当你认为你要处理的数是有符号的,那么你就用那一套处理有符号数的指令,当你认为你要处理的数是无符号的,那就用处理无符号数的那一套指令,计算机只根据指令对每一位进行计算,并不在乎这个二进制数据是有符号还是无符号,这就需要依靠程序员判断处理的数值是有符号还是无符号,仅此而已。
加减法只有一套指令,因为这一套指令同时适用于有符号和无符号(刚才所说的补码优点)。而有些指令,如:mul div movzx … 是处理无符号数的,而这些:imul idiv movsx … 是处理有符号的(也就是针对有符号和无符号提供两套指令)
举例来说:
我们在汇编中,向内存中放入两个数据,一个字节x 为:0x EC ,一个字节 y 为:0x 02 。
发现没有负号,都是正数,OK那么最高位的负号位设置为0,再对x进行截断处理,得到
x:
1 1 1 0 1 1 0 0
y:
0 0 0 0 0 0 1 0
注意这里的x,如果x是个有符号数字,虽然我们给的是一个ec正数,但是由于截断处理,最高位为1,成了一个负数,又由于这是补码保存,原码就成了-20,y仍然为2
当作无符号数看时,第一位的符号位不看,x = 236 ,y = 2 。
但是从计算机的角度看,这就是一串二进制,哪有什么有符号无符号数字
下面用 add 指令进行加法运算,计算机开始工作,它只需要把每一位相加,仅此而已,它才不分什么有符号,无符号。
结果:
1 1 1 0 1 1 1 0
这个结果当作有符号数就是:-18 ,无符号数就是 238 。同样,计算机认为这仍然是一串二进制,所以add 一个指令可以适用有符号和无符号两种情况。(呵呵,其实为什么要补码啊,就是为了这个呗,:-))
乘法运算就不行了,必须用两套指令,有符号的情况下用imul 得到的结果是:0x FF D8 就是 -40 。无符号的情况下用 mul ,得到:0x 01 D8 就是 472 。
三、 OF、CF、SF标志
为什么说着三个标志位,因为我对汇编中无符号,有符号的疑惑就是从这三个标志位的学习而产生的。
先看CF标志位,书上说CF标志位只对无符号数有意义,首先明白一点,即使是两个有符号数相加,也会导致CF的变动,并不是说有符号数,编译器不设置CF位。
因为CF的标志位的变动是由于最高有效位(如果对于8位数,就是第8位)向更高位(第9位)产生了进位或者借位而产生,而对于有符号数来说,最高位是符号位,它的变动和数值位的变动意义不一样。所以对于有符号数,CF也可能发生变动,但是它的变动是没意义的。而如果是无符号数,它的变动就意味中8位的内存或寄存器不足以保存数据,因为数据产生了进位或借位。
再看OF标志位,它只对有符号数有意义,因为两个标准的8位有符号数据(标准指的是赋值的时候不要赋超过有符号数范围的数字,由于截断,即是8位能保存,保存进来的数据数值大小早就产生了变化),这2个数据只有同号(都为正或为负)相加才会溢出,也就是结果超过有符号数的范围。例如2个正数,符号位(第8位)都为0,相加后发生溢出,符号位由于第7位的进位变成了1,两个正数相加变为了负数?由此对OF产生了作用,如此来说OF的作用是由于符号位发生变化,如果是两个无符号数,最高位代表的并不是符号意义,产生了变动也是无意义的,所以说OF只对有符号数有意义。
最后SF标志,有了上面的介绍,就能理解SF看的是最高位的符号位意义,对于无符号数来说,最高位代表的是数值意义,并不是符号意义。
四、可爱又可怕的c语言。
为什么又扯到 c 了?因为大多数遇到有符号还是无符号问题的朋友,都是c里面的 signed 和 unsigned 声明引起的,那为什么开头是从汇编讲起呢?因为我们现在用的c编译器,无论gcc 也好,vc6 的cl 也好,都是将c语言代码编译成汇编语言代码,然后再用汇编器汇编成机器码的。搞清楚了汇编,就相当于从根本上明白了c,而且,用机器的思维去考虑问题,必须用汇编。(我一般遇到什么奇怪的c语言的问题都是把它编译成汇编来看。)
C 是可爱的,因为c符合kiss 原则,对机器的抽象程度刚刚好,让我们即提高了思维层面(比汇编的机器层面人性化多了),又不至于离机器太远 (像c# ,Java之类就太远了)。当初K&R 版的c就是高级一点的汇编……:-)
C又是可怕的,因为它把机器层面的所有的东西都反应了出来,像这个有没有符号的问题就是一例(java就不存在这个问题,因为它被设计成所有的整数都是有符号的)。为了说明c的可怕特举一例:
#include <stdio.h>
#include <string.h>
int main()
{
int x = 2;
char * str = "abcd";
int y = (x - strlen(str) ) / 2;
//注:原作者这样写,编译器可能会对其优化,直接使用右移移位指令而不是采用除法指令,改成3即可看到
printf("%d\n",y);
}
结果应该是 -1 但是却得到:2147483647 。为什么?因为strlen的返回值,类型是size_t,也就是unsigned int ,与 int 混合计算时,int类型被自动转换为unsigned int了,结果自然出乎意料。。。
观察编译后的代码,除法指令为 div ,意味无符号除法。解决办法就是强制转换,变成 int y = (int)(x - strlen(str) ) / 2; 强制向有符号方向转换(编译器默认正好相反),这样一来,除法指令编译成 idiv 了。我们知道,就是同样状态的两个内存单位,用有符号处理指令 imul ,idiv 等得到的结果,与用 无符号处理指令mul,div等得到的结果,是截然不同的!所以牵扯到有符号无符号计算的问题,特别是存在讨厌的自动转换时,要倍加小心!(这里自动转换时,无论gcc还是cl都不提示!!!)
为了避免这些错误,建议,凡是在运算的时候,确保你的变量都是 signed 的。(完)
部分内容转载自:
http://blog.csdn.net/xuezhongfenfei/article/details/8351838
并对其内容进行修改、注释和扩充,感谢原作者的分享