关于unsigned int的 问题

我在别的论坛看见的,感觉对自己有帮助,就转载过来了。

原帖地址:http://bbs.bccn.net/thread-374006-1-1.html

关于unsigned int的 问题
#include <stdio.h>
main()
{int a,b,c;
unsigned u,d;
a=12;b=-24;u=10;c=a+u;d=b+u;

printf("a+u=%d,b+u=%d\n",c,d);
}

我定义的d是无符号的整型变量,算出来的d=b+u=-14,但因为d是无符号的整型变量,不应该显示的是14吗?
为什么我的执行结果却是b+u=-14.  无符号的变量能和有符号的变量做运算么? 结果若定义位unsigned的就是无论正负都是正的么?

提示一下,撇开前面的变量定义,你看看下面的语句输出什么:
printf("%u\n", -1);
printf("%d\n", -1);
注意:两次printf()都是使用同一个数!

问题本质就是:C语言的数据是由约定来解释的,这是C面向底层的原因。同样的一个二进制编码,被理解成什么,其实不是语言本身规定的,而是程序员自己决定的,这点是C语言与其他编程语言区别很大的一个特性。其实你一开始就学过,对ASCII字符'A',它的编码是65,在程序的机器码中,这就是一个十进制整数65,当你决定要把它解释成字符并命令它输出时,就应该使用printf("%c", 65),这等于告诉printf()函数,“我要把整数65解释成字符,你替我把对应的字符打印出来”,而如果你不要字符,也可以这样printf("%d", 'A'),这时,输出的是65(正如前面告诉你'A'实际上就是以65储存的)。C++沿袭了C的这种char等效于int的特性,是迫不得已,但在其他的高级编程语言中,都力戒这种语法的,即使在机器内部确实'A'=65,但在语言代码中坚决不允许你写出char x = 65这样的东西,这样规定,都是有深意的。

所以,在C语言中,不要被自己声明的数据类型弄混。你完全可以定义unsigned int x = -1,但-1在计算机中无论你声明x是signed int还是unsigned int,它都是0xffffffff(对32位整数来说),在运算中,需要程序员自己警惕到底使用的是什么!最典型的陷阱,就是for (unsigned int i = 10; i >= 0; --i)这样的循环语句,此时变量i被编译器解释为无符号整数,永远是i>=0的,这是一个死循环,因为当--i到i=0,再继续--i时,你以为是-1,但实际上机器认为那是0xffffffff,是一个极大的正整数。

事实上,不是所有语言都认可unsigned这样的无符号数据类型,有些语言是不承认这种数据的,原因也在这里。在C编程规范中,也劝告程序员尽量使用常规int类型,这才是保证程序可移植性的编码。因此,说C语言程序的可移植性强,不要轻信,除非你确实遵守规则,一旦培养了坏习惯,优势也变劣势。


比如下面这个

程序代码:
     int main( void) {
         unsigned  int a =  1000;
         int b = - 1;
         if(a > b)
            printf( " a is big¥n ");
         else
            printf( " a is small¥n ");
         return  0;
    }


输出结果是 a  is small
先把b 转成unsigned int
再比较,得出的a 小的结论
可是为什么要把b先转成unsigned int?
下面的帖子有解释,可是没怎么看懂尤其是

Binary operations between different integral types are performed

within a "common" type defined by so called usual arithmetic conversions

(see the language specification, 6.3.1.8).
In your case the "common" type is unsigned int.

查了一下language specification, 6.3.1.8,
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
参考了一下,是不是
两个需要比较的数值,如果一个数值转换成另一种类型,

并且能很好的被表示出来,那这个数值就不被转换

所以1000 被转成int的话也可以被毫无压力的表示出来,所以1000不被转换,

所以b=-1则被转换为unsigned int?再进行比较?



这应该是基于编译器作的一种推断:既然你作比较的两个数据,有一个是unsigned的,那么另外一个如果是负数,这样的比较是没有意义的,它猜测程序员很聪明,不会这样做结果明显的比较,所以干脆把另外的一个也解释为unsigned类型,这样做比较才是有意义的。事实上,你明知有一个数是无符号的,还拿它与有符号的数比较干什么呢?


类型声明在多个地方影响编译器的行为,编译器编译的结果左右程序运行。printf()中的输出符,只是printf()这个具体函数的一种行为罢了。比如11楼那个比较代码,int被隐性转型为unsigned int,就不是输出符导致的。

程序代码:
一、只有一个标准!

在汇编语言层面,声明变量的时候,没有  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 。当把x,y当作有符号数来看时,x = - 20 ,y = + 2 。当作无符号数看时,x =  236 ,y =  2 。下面进行加运算,用 add 指令,得到的结果为:0x EE ,那么这个 0x EE 当作有符号数就是:- 18 ,无符号数就是  238 。所以,add 一个指令可以适用有符号和无符号两种情况。(哈哈,其实为什么要补码啊,就是为了这个呗,:-))
乘法运算就不行了,必须用两套指令,有符号的情况下用imul 得到的结果是:0x FF D8 就是 - 40 。无符号的情况下用 mul ,得到:0x  01 D8 就是  472 。(参看文后附录2例程)

三、可爱又可怕的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;

printf( " %d\n ",y);
}

结果应该是 - 1 但是却得到: 2147483647 。为什么?因为strlen的返回值,类型是size_t,也就是unsigned  int ,与  int 混合计算时类型被自动转换了,结果自然出乎意料。。。
观察编译后的代码,除法指令为 div ,意味无符号除法。
解决办法就是强制转换,变成  int y = ( int)(x - strlen(str) ) /  2; 强制向有符号方向转换(编译器默认正好相反),这样一来,除法指令编译成 idiv 了。我们知道,就是同样状态的两个内存单位,用有符号处理指令 imul ,idiv 等得到的结果,与用 无符号处理指令mul,div等得到的结果,是截然不同的!所以牵扯到有符号无符号计算的问题,特别是存在讨厌的自动转换时,要倍加小心!(这里自动转换时,无论gcc还是cl都不提示!!!)


为了避免这些错误,建议,凡是在运算的时候,确保你的变量都是  signed 的。



遵守人类的逻辑思维写代码,出现那些错误的机会就会减到最少。再三忠告:不要玩技巧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值