一个由无符号类型引发的微妙的错误
#include <stdio.h>
#include <string.h>
int main()
{
char array[] = "hello";
int d = -1, x;
//(1)//if (d <= sizeof(array) / sizeof(char))
//(2)//if (d <= strlen(array))
//if (d <= (int)sizeof(array) / sizeof(char)) /*输出:x = 104*/
if (d <= (int)strlen(array)) /*输出:x = 104*/
{
x = array[d + 1];
printf("x = %d\n", x); //字符'h'对应的ASCII码是104, 'a'是97
}
else
{
printf("sizeof(array) / sizeof(char) = %u\n", sizeof(array) / sizeof(char));
printf("strlen(array) = %u\n", strlen(array));
//用%d输出也是一样,都是正数
}
return 0;
}
- 这个程序若用(1)或(2)的判断条件会输出:
sizeof(array) / sizeof(char) = 6
strlen(array) = 5
加上强制类型转换(int)才会输出:x = 104
(1)原因是sizeof操作符(注意它不是一个函数,也可以有 sizeof char;
和sizeof(char);
效果一样)的返回值类型是size_t
,而typedef unsigned int size_t;
所以在执行表达式d <= sizeof(array)/sizeof(char)
时,右边表达式结果为无符号整型6,因此会将左边的整型d装换成无符号整型(-1(在计算机中以1的补码形式存在为二进制1111...11111)将变成变成一个超级大的整数),因此判断结果是false。
改正方法:
d <= (int)sizeof(array)/sizeof(char)
,人为增加强制类型转换,这样就不必由编译器来选择结果的类型了。
(2)同理,strlen()函数的返回值类型也是size_t
,而typedef unsigned int size_t;
。但是注意strlen()函数和sizeof操作符的区别,sizeof会多计算一个结束符'\0'的大小,因此会多1,而strlen()函数总是只计算实际字符串字符个数。
改正方法:
d <= (int)strlen(array)
,人为增加强制类型转换,这样就不必由编译器来选择结果的类型了。
对无符号类型的建议(《C专家编程》第24页)
1、尽量不要在代码中使用无符号类型,以免增加不必要的麻烦,尤其是,不要仅仅因为无符号类型不存在负值(如年龄)而用它来表示数量,可以多增加if-else判断数量的合法性也比用无符号类型更安全。否则一个int的 -1,由编译器自动升级成无符号型int,将会变成一个非常大的数。
2、如果用了,应该在表达式中使用强制类型转换,使操作数均为有符号或者无符号类型,这样就不必由编译器来选择结果的类型,可以避免微妙的错误发生。
3、只有在使用位段和二进制掩码时,最好去用无符号数。这里就要谈到正数和负数在计算机中的二进制存储方式了。正数简单,就是原码二进制形式存储,如int型32bit位int a = 1;
那么a在计算机中存储为二进制000...(总共31个0)...0001。但是负数就比较复杂,要用原码求反码再加1得到的补码的二进制形式存储在计算机中,如int b = -1;
原码和a一样,反码就是111...(总共31个1)...1110,然后反码加1得到补码111...(总共32个1)...1111。可见b并不是简单的存储为二进制100...(总共30个0)...0001。因此在使用位段和二进制掩码这些需要按bit位操作的情况下,为了在使用时让我们脑子里想的和计算机处理的一致,尽量都用无符号类型,就可以避免负数在计算机中的特殊存储方式带来的影响。
《注意:负数要以10进制输出时,又要将计算机中存储的补码,先减1求得反码,再按位求反得到原码,然后将原码转换成10进制输出并在前面添加一个‘-’号》
使用无符号类型的优缺点
- 比如int类型和unsigned int类型,同样是占用4字节(32bit位),int类型的取值范围是(-2^31到2^31),而unsigned int类型的取值范围是(0到2^32),可见在利用正数的这一半时无符号数的可用范围是有符号的两倍。有符号数最高bit位的0或1只能表示+-符号,不能表示数值,浪费了。