-1>1?! unsigned int的世界不简单

编程语言提供了很多的基本数据类型,比如char,int,float,double等等。在C和C++的世界中,还有一种类型,叫做无符号数据,修饰符位unsigned,比如今天要说的unsigned int。引入特殊的类型,一方面带来了好处,一方面也留下了隐患。

一、有符号数与无符号数谁大谁小

上代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a = -1;
    unsigned int b = 1;

    if(a > b)
        printf("a > b, a = %d, b = %u
", a, b);
    else
        printf("a <= b, a = %d, b = %u
", a, b);

    return 0;
}

结果为:
-1>1?! unsigned int的世界不简单

什么?-1竟然大于1?从结果上看,的确是这样的。为什么从这样呢?这样从C++对同时包含有符号数与无符号数的表达式的处理说起。

二、C++底层怎么处理的

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

对应上面的例子,就是先把-1这个有符号数强制转换成无符号数,再与1比较,并假设两个数都是非负的。那么-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进制数的形式打印。

#include <stdio.h>
#include <stdlib.h>

void show_bytes(unsigned char *start, int len)
{
    int i = 0;
    for(; i < len; ++i)
        printf(" %.2x", start[i]);
    printf("
");
}

int main()
{
    int a = -1;
    unsigned int b = 4294967295;

    printf("a = %d, a = %u
", a, a);
    printf("b = %d, b = %u
", b, b);

    show_bytes((unsigned char*)&a, sizeof(int));
    show_bytes((unsigned char*)&b, sizeof(unsigned int));

    return 0;  
}

结果为:
-1>1?! unsigned int的世界不简单

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