有符号类型数据带来的原码、反码、补码思考

来源:互联网 发布:无锡华云数据面试 编辑:程序博客网 时间:2024/06/04 19:28

前段时间发现一个比较有趣的编程问题:
下面的程序输出什么?

void func(){    unsigned int a = 5u;    int b = -10;    if( (a + b) < 0 ){        printf("5 - 10 < 0\n");    }    else        printf("5 - 10 >0\n");}int main(int argc,char** argv){    func();    return EXIT_SUCCESS;}

结果

相信上当者十之八九认为: 5 - 10 == -5;-5 < 0成立;程序输出:5 - 10 < 0。然后就让出题者贻笑大方了。

估计是个程序员都知道一个常识,那就是冯诺依曼计算机推翻了传统的十进制计数方式,采用二进制计数,这一体系结构的确定,突破了当时开发模拟式计算机的技术瓶颈,数字式计算机自此飞速发展。(貌似说了一句废话…)

==============================================
很显然,这个题目考察了:
1.编译器对不同数据类型做运算时的默认类型转换处理;
2.有符号类型数据在内存中的补码存储。

==============================================
1.C/C++编译器默认的类型转换:
计算机最初设计,就是为了做科学计算,针对这种设计初衷,当面对不同数据类型做运算的时候,计算机的设计者肯定希望的是计算机能够完成这一运算。尽管早期的计算机内存资源并不丰富,但是为了完成不同类型之间的数据的运算,计算机设计者还是忍痛地做了这么一个决定:当两个数据类型不同的对象进行运算的时候,其运算结果总是以能够存储更大数据值的形式存储。比如说:int 和 char 做 + 运算,那么他们的结果就以int的形式存储,因为int能够存储的容量更大;而unsigned int和int做运算,结果则是以ungined int的形式存储,因为unsigned int能够存储的容量更大。

==============================================
2.有符号类型数据在内存中的补码存储。
C语言中,原码、反码、补码都是有符号定点数的表示方法。
一个有符号定点数的最高位为符号位,0代表正,1代表负。
以下以8位整数为例:
原码就是这个数本身的二进制形式。
例如
1000 0001 就是-1
0000 0001 就是+1

正数的反码、补码和原码都相同。

对于负数而言
反码 = 原码的符号位不变,其余各位取反。
补码 = 反码+1
举例8位-1而言:
其原码 = 1000 0001
其反码 = 1111 1110
其补码 = 1111 1111

这里引出一个困扰我许久的问题:一段8位的内存空间存储1000 0001,那么,这个数据到底是有符号的数据-1呢?还是无符号数据129呢?到底怎么识别?很不幸的,这是一个无解的问题,就如你想的那样,没有办法直接识别出一个最高位为1二进制数到底是有符号数还是无符号数。至于编译器为什么能够识别出,我猜想的是编译器内部在对变量进行内存开辟的时候做了标识符标定吧(此处如果有高见还望多多指教)。

==============================================
铺垫结束,回到题目当中,在func函数中,定义了unsigned int a;和int b;
当a + b运算出现时,站在编译器的角度来看,需要把b转换为无符号类型的数据,然后再与a做加法运算,最终结果与0比较。编译器是这么想的,它也这么做了。假设在32位系统中,这样做导致b(-10)(1<27个0>1010)被识别为2147483658u,然后 a(5u) + 2147483658u == 2147483663u;由此,2147483663u < 0显然是不成立的;所以,程序最终输出是表面上看上去的十分不可思议的:5 - 10 > 0;然而它确确实实发生了。

==============================================
带来一点关于数据移位的扩展思考
在进行软件开发的时候,我们经常会对数据进行移位运算,思考一个问题:移位运算的时候,数据缺失掉的位一直都是补0吗?
不是这样的,进行移位运算,对缺失数据位的填补,有时候也补1。那么移位运算补1和补0什么时候发生?总结如下:

对于无符号数,无论数据是左移(<<)还是右移(>>),数据缺失的位永远都是填补0。
例如

unsigned char uCharData = 0xFF;//左移1位uCharData  <<= 1;printf("when << 1 bit, uCharData = %x\n",uCharData); //十六进制打印//重置数据并右移1位uCharData = 0xFF;uCharData >>= 1;printf("when << 1 bit, uCharData = %x\n",uCharData); //十六进制打印

输出结果:
无符号移位输出
显然,无论左移还是右移,往缺失位上填充的数据都是0。

对于有符号数据而言,当发生左移(<<)时,缺失的数据为填补0,发生右移时,缺失的数据为填补1。

    int intData = -1;    //左移1位    intData  <<= 1;    printf("when << 1 bit, intData = %x\n",intData); //十六进制打印    //重置数据并右移1位    intData = -1;    intData >>= 1;    printf("when << 1 bit, intData = %x\n",intData); //十六进制打印

有符号整形
值得注意的是,这次使用了int类型的数据,它是有符号类型数据,程序运行在64位机器上。

-1在存储在内存中的补码为0xffffffff,左移1位,变成0xfffffffe,说明左移时候对缺失位补0处理;当右移1位之后,内存中存储的数据还是0xffffffff,说明,右移之后,缺失的位填补的是1,

阅读全文
2 0
原创粉丝点击