正如我们所知道的,编程语句都有很多的基本数据类型,如char,inf,float等等,而在C和C++中还有一个特殊的类型就是无符号数,它由unsigned修饰,如unsigned int等。大家有没想过,就是因为这些不同的类型,而使大家编写的看似非常正确的程序出现了预想不到的错误呢?

一、迷惑人的有符号下无符号数的比较操作

废话不多说,马上来看一下例子,让你先来体验一下这个奇妙的旅程,源代码文件名为unsigned.c,源代码如下:

[cpp] view plain copy  print?

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. int a = -1;
  6. unsigned int b = 1;
  7. if(a > b)
  8. printf("a > b, a = %d, b = %u\n", a, b);
  9. else
  10. printf("a <= b, a = %d, b = %u\n", a, b);
  11. exit(0);
  12. }

输出结果为:

看到输出结果之后,你可能会大吃一惊,-1竟然大于1,你没有看错,从输出结果上来看的确是这样。为什么会产生这样的结果呢?这还得从C语言对同时包含有符号数和无符号数表达式的处理方式讲起。

二、有符号数与无符号运算时数强制类型转换方式及底层表示

当执行一个运算时(如这里的a>b),如果它的一个运算数是有符号的而另一个数是无符号的,那么C语言会隐式地将有符号 参数强制类型为无符号数,并假设这两个数都是非负的,来执行这个运算。这种方法对于标准的算术运算来说并无多大差异,但是对于像<和>这样的运算就可能产生非直观的结果。

所以对应回上面的例子,就是它先把-1(变量a的值)这个有符号数强制转换成无符号数,然后再与1(变量b)的值,来进行比较,并假设这两个数原本都是非负的,然后进行比较。那么-1转换为无符号数后,其值为多少呢?你可以写一个小小的程序来验证一下,在32和64位的机子上,-1对应的无符号数应该是4 294 967 295,即32位的无符号数的最大值(UMax),所以if中的条件总是为真。

要想这段代码正常执行,我们需要怎么办呢?很简单,把if语句改为if(a > (int)b)即可。这样程序就会认为是两个有符号数在进行比较,-1就不会隐式地转换为无符号数而变成UMax。

可能你已经有一个问题,为什么使用强制类型,把变量b的类型变成int程序就能正常,而-1转换成无符号数为什么会是4 294 967 295呢?这就得从整型数据在计算机中的表示和C语言对待强制类型转换的方式说起。

我们知道,整数在计算机中通常是以补码的形式存在的,而-1的补码(用4个字节储存)为1111,1111,1111,1111。而C语言对于强制类型转换是怎么处理的呢?对大多数C语言的实现,处理同样字长的有符号数和无符号数之间的相互转换的一般规则是:数值可能会改变,但是位模式不变。也就是说,将unsigned int强制类型转换成int,或将int转换成unsigned int底层的位表示保持不变。

也就是说,即使是-1转换成unsigned int之后,它在内存中的表示还是没有改变,即1111,1111,1111,1111。我们知道在计算机的底层,数据是没有类型可言的,所有的数据非0即1。数据类型只有在高层的应用程序才有意义,也就是说,同样的储存表示对于应用程序而言可能对应着不同的数据,例如1111,1111,1111,1111对于有符号数而言它表示-1,但对于无符号数而言,它表示UMax,但是它们的底层存储都是一样的。现在你应该明白为什么-1转换成无符号数之后,就成了UMax了吧。

三、查看数据的底层表示

为了证明上面所说的内容,请再看下面的代码,里面有个函数show_byte,它可以把从指针start开始的len个字节的值以16进制数的形式打印出来。源文件为showbyte.c,代码如下:

[cpp] view plain copy  print?

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. void show_bytes(unsigned char *start, int len)
  4. {
  5. int i = 0;
  6. for(; i < len; ++i)
  7. printf(" %.2x", start[i]);
  8. printf("\n");
  9. }
  10. int main()
  11. {
  12. int a = -1;
  13. unsigned int b = 4294967295;
  14. printf("a = %d, a = %u\n", a, a);
  15. printf("b = %d, b = %u\n", b, b);
  16. show_bytes((unsigned char*)&a, sizeof(int));
  17. show_bytes((unsigned char*)&b, sizeof(unsigned int));
  18. exit(0);
  19. }

输出为:

分析:printf函数中,%u表示以无符号数十进制的形式输出,%d表示以有符号十进制的形式输出。通过show_bytes函数,我们可以看到,-1与4 294 967 295的底层表示是一样的,它们的位全部都是全1,即每个字节表示为ff。

四、由于无符号数减法引起的错误

你可能会说,你不会用一个无符号数与一个有符号数作比较,所以你觉得你可以放心了,但是来看看下面的两段代码。

代码1是一个求数组中前length个数据的和的函数,数组中元素的个数由参数length给出,代码如下:

[cpp] view plain copy  print?

  1. float sum_elements(float a[], unsigned length)
  2. {
  3. int i = 0;
  4. float sum = 0;
  5. for(i = 0; i <= length -1; ++i)
  6. sum += a[i];
  7. return sum;
  8. }

如果我告诉你这是一段有错的代码,可能你也不太相信,因为这个函数的一切看起来是这么的自然,因为数据的长度(或个数)肯定是一个非负数,所以把length声明为一个unsigned很合理,计算的数据个数和返回类型也正确。的确如此,但是这都是在length不为0的情况,试想,当调用函数时,把0作为参数传递给length会发生什么事情?回想一下前面我们所说的知识,因为length是unsigned类型,所以所有的运算都被隐式地被强制转换为unsigned类型,所以length-1(即0-1 = -1),-1对应的无符号类型的值为UMax,所以for循环将会循环UMax次,数组也会越界,发生错误。那么如何优化上面的代码呢?其实答案非常简单,你也可以自己想一想,这里就给出答案吧,就是把for循环改为:

[cpp] view plain copy  print?

  1. for(i = 0; i < length; ++i)

因为去除了length-1,所以当length为0时也能正常比较。

接下来是代码2,它是一个判断第一个字符串是否长于第二个字符串,若是,返回1,若否返回0,代码如下:

[cpp] view plain copy  print?

  1. int strlonger(char *s1, char *s2)
  2. {
  3. return strlen(s1) - strlen(s2) > 0;
  4. }

如果我又跟你说这段代码是有bug,你现在找不找得出来呢,还是认为这段代码是没有任何问题的呢?说真的就这么看这个函数好像的确是没有什么问题,但是如果你知道了strlen函数的原型,可能你就会有点明白了,在Linux下可用man 3 strlen命令查看,strlen函数的原型为:

[cpp] view plain copy  print?

  1. size_t strlen(const char *s);

注意这里有一个数据类型size_t,它被定义在stdio.h文件中,其实它就是unsigned int,一个字符串的长度当然不可能为负,这样的定义显然是合理的,但是有时却因为这样,而存在不少的问题,如函数strlonger的实现。当s1的长度大于等于s2时,这个函数并没有什么问题,但是你可以想像,当s1的长度小于s2的长度时,这个函数会返回什么吗?没错,因为此时strlen(s1) - strlen(s2)为负(从数学的角度来解释的话),而又由于程序把它作为unsigned为处理,则此时的值肯定是一个比0大的值。换句话来说,这个函数只有在strlen(s1) == strlen(s2)时返回假,其他情况都返回真。

下面是我的测试代码:

[cpp] view plain copy  print?

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. int strlonger(char *s1, char *s2)
  5. {
  6. return strlen(s1) - strlen(s2) > 0;
  7. }
  8. int main()
  9. {
  10. char s1[] = "abc";
  11. char s2[] = "cd";
  12. if(strlonger(s1, s2))
  13. printf("s1 is longer than s2, s1 = %s, s2 = %s\n", s1, s2);
  14. else
  15. printf("s1 is shorter than s2, s1 = %s, s2 = %s\n", s1, s2);
  16. if(strlonger(s2, s1))
  17. printf("s2 is longer than s1, s2 = %s, s1 = %s\n", s2, s1);
  18. else
  19. printf("s2 is shorter than s1, s2 = %s, s1 = %s\n", s2, s1);
  20. }

运行结果如下:

从运行结果来看,确实如此,只要s1与s2长度不等,就返回真。那么我们在怎么样改善这段代码呢?其实答案也是很简单的,所函数改为如下即可:

[html] view plain copy  print?

  1. int strlonger(char *s1, char *s2)
  2. {
  3. return strlen(s1) > strlen(s2);
  4. }

这样就可以利用两个无符号数进行直接的比较,而不会因为减法而出现负数(数学上来说)而影响比较结果。

五、建议

这么看来,unsigned还真是一个危险的东西,大家还是要谨慎使用啊。其实个人建议,没有什么必要的原因,就不要使用unsigned,即使有时它看起来是那么的合理,因为有它在的运算,很多时候会产生非直观的错误,而且这种错误还非常难发现。如果你要使用的话,则尽量避免有符号数与无符号数的比较运算和避免减法运算,在很多时候,在unsigned的世界里,x-y>0与x>y都是不等价的。

有符号数与无符号数比较-详解相关推荐

  1. 有符号数和无符号数详解

    有符号数和无符号数详解 1. 通过例子看问题 2. 有符号数和无符号数 2.1 什么是无符号数 ? 2.2 什么是有符号数 ? 2.3 有符号数和无符号数的区别 3. 原码.反码.补码 3.1 背景 ...

  2. 有符号数和无符号数详解(2)补码详解

    有符号数和无符号数详解(2)补码详解 1. 为什么需要补码 1.1 背景 2 补码的思想 2.1 我们希望只设计加法运算器,不用减法运算器. 2.2 现在问题是:怎么表示-1呢? 3. 补码 3.1 ...

  3. java中有符号数和无符号数,C语言中无符号数和有符号数之间的运算

    C语言中有符号数和无符号数进行运算(包括逻辑运算和算术运算)默认会将有符号数看成无符号数进行运算,其中算术运算默认返回无符号数,逻辑运算当然是返回0或1了. unsigned int和int进行运算 ...

  4. 理解有符号数和无符号数的区别

    理解有符号数和无符号数 回头看上一节,我们所讲的数都是正数.同样是年纪和工资,前者不需要有负值,但后者可能需要--至少所有的老板都这样认为. 那么,负数在计算机中如何表示呢? 这一点,你可能听过两种不 ...

  5. c语言中的无符号字节,C语言之有符号数和无符号数

    我们知道,在C语言中存在无符号数和有符号数(一些高级语言如Java里面是没有无符号数的),但是对于计算机而言,其本身并不区别有符号数和无符号数,因为在计算机里面都是0或者1,但是在我们的实际使用中有时 ...

  6. C\C++不经意间留下的知识空白------有符号数和无符号数

    C和C++ C和C++应该是大多数工科生最先接触的两门语言,个人感觉这两门还是挺难的.今天在看面试题时,看到有符号数和无符号数时竟然懵了,这基础是有多不扎实啊! 有符号数和无符号数 对于浮点数来说都是 ...

  7. 各种类型sizeof大小及C++有符号数与无符号数进行比较

    不同类型sizeof相关: class A {}; class B { char m_data;}; class C {char m_data[100]; }; class D {char* m_da ...

  8. 11-有符号数和无符号数

    1. 有符号数和无符号数 无符号数,因为没有符号位,所以只能表示一个正数. 有符号数,因为存在符号位,符号位如果是0的话,代表这是一个正数,符号位如果是1的话,代表这个数是一个负数. 我们可以用db伪 ...

  9. C - 有符号数和无符号数扩展

    C语言标准要求先进行数据大小的转换,之后再进行无符号和有符号之间的转换. C语言中的强制类型转换保持二进制位值不变,只是改变解释位的方式. 将无符号数转换为更大的数据类型时, 只需简单地在开头添加0, ...

最新文章

  1. 数据中心是虚拟现实的基石
  2. python代码编辑器排行榜-4款好用的Python编辑器,你用过几个?
  3. HDU - 5157 Harry and magic string(回文自动机)
  4. 网易严选退出双十一:“抵制”鼓吹过度消费
  5. python神奇时钟项目_怎么在Python项目中利用Pygame绘制一个时钟
  6. FFT节省资源的思路
  7. 2021-06-28操作表单
  8. sort()函数关于结构内容要怎么写
  9. Java Integer 对象的比较
  10. 思维导图软件列表(mind mapping software list)
  11. 瑞禧靶向光热染料ICG偶联-吲哚菁绿标记Labeled泛素/黑接骨木凝集素SNA / EBL
  12. C语言 学生成绩统计
  13. 服务器网络协议是什么,介绍网络协议,什么是网络协议三要素?
  14. 易订货专属App文档
  15. python集成开发环境中可使用什么快捷键运行程序_1.4 Python集成开发环境(2)
  16. SEED-XDS560v2 驱动
  17. 单相交流电和三相交流电概念扫盲
  18. 视频聊天的java代码
  19. Python常用网络爬虫速查表下载
  20. 工业物联网的开放语义框架

热门文章

  1. vijos p1304 回文数
  2. android adc按键原理,看完这篇文章,终于搞懂了ADC的原理及分类!
  3. 微搭发布的应用配置到企业微信的侧边栏
  4. 外企看好中国外包业务市场
  5. 百度网盘百度云视频倍速播放方法 亲测有用 共6种,总有一个适合你
  6. 【Cocos2D-X 游戏引擎】初窥门径(1) 制作一个动态的精灵
  7. [4G5G专题-88]:功能 - 4G LTE-Advanced关键技术概述
  8. 使用vue-handsontable遇到的坑以及解决办法
  9. 其实很简单 微星为你详解Z77主板BIOS设置
  10. stm32的温湿度监控