算术类型转换
首先给出一段C代码:
int main(void)
{
if (-1 < (unsigned char)1)
printf("true, ANSI C semantics\n");
else
printf("false, K&R C semantics\n");
return 0;
}
这段程序分别在ANSI C和K&R C编译器下编译的话,运行的结果是不同的。-1的位模式是一样的,但ANSI C编译器将它解释为有符号数,是个负数。K&R C编译器却将它解释为无符号数,变成了一个非常大都正数。这是因为两个标准在做算术类型转换时使用的规则不同导致的。
在《The C Programming Language》A.6算术类型转换 章节详细描述了ANSI C的转换规则。
许多运算符都会以类似的方式在运算过程中引起转换,并产生结果类型。其效果是将所有操作数转换为同一公共类型,并以此作为结果的类型。这种方式的转换称为普通算术类型转换。
首先,如果任何一个操作数为long double类型,则将另一个操作数转换为long double类型。
否则,如果任何一个操作数为double类型,则将另一个操作数转换为double类型。
否则,如果任何一个操作数为float类型,则将另一个操作数转换为float类型。
否则,同时对两个操作数进行整形提升(integral promotion)。所谓整形提升,就是在一个表达式中,char,short,int型位域(bit-field),包括它们的有符号类型或者无符号类型,以及枚举类型等这些类型都需要提升为int或者unsigned int类型。如果原始类型的所有值都可用int类型表示,则其值将被转换为int类;否则将被转换为unsigned int类型。
整形提升之后,如果任何一个操作数为unsigned long int类型,则将另一个操作数转换为unsigned long int类型。
否则,如果任何一个操作数为long int类型且另一个操作数为unsigned int类型,则结果依赖于long int类型是否可以表示所有的unsigned int类型的值。如果可以,则将unsigned int类型的操作数转换为long int类型。如果不可以,则将两个操作数都转换为unsigned long int类型。
否则,如果任何一个操作数为long int类型,则将另一个操作数转换为long int类型。
否则,如果任何一个操作数为unsigned int类型,则将另一个操作数转换为unsigned int类型。
否则,将两个操作数都转换为int类型。
所以ANSI C采用的是值保留(value preversing)原则,尽量保证不会发生溢出。而K&R C的规则比较简单,采用的是无符号保留(unsigned preversing)原则。就是当一个无符号类型与int或更小的整形混合使用时,结果类型是无符号类型,这个规则简单,与硬件无关,但是有时会使一个负数丢失符号位。
结合上例,在ANSI C编译器下,unsigned char的1被提升为int类型,所以if条件为真。但是在K&R C编译器下,由于<操作符右边是unsigned char类型,所以<操作符左边会转化为unsigned int,结果是unsigned int,所以if条件为假。
这里提到K&R C标准,是为了说明在说明ANSI C类型转换时,有个对比。在实际项目中,应该都遵从ANSI C的规范,这样代码可最大限度地保证程序的可移植性。
给出另一段C代码:
int array[] = { 23, 34, 12, 17, 204, 99, 16 };
#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))
int main(void)
{
int d = -1, x = 0;
if (d <= TOTAL_ELEMENTS - 2)
x = array[d+1];
printf("x=%d\n", x);
return 0;
}
根据标准C的规则,因为sizeof是unsigned类型,所以<=操作符左边会转化为unsigned,因此x=0.需要修正该问题,可以做如下改动:
if (d <= (int)TOTAL_ELEMENTS - 2)
x = array[d+1];
关于无符号类型的建议
关于无符号类型的建议:
-
尽量不要在你的代码中使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在负值(如年龄,国债)而用它来表示数量。
-
尽量使用int那样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况(如-1变为非常大的正数)。
-
只有在使用bit-field和二进制掩码时,才使用无符号数。应该在表达式中使用强制类型转换,使操作数均为有符号数或者无符号数,这样不必由编译器来选择结果的类型。
关于ANSI C的整形提升的补充
C语言中的类型转换比一般人想象中的要广泛的多。看个例子:
printf("%d %d", sizeof('A'), sizeof(char));
这行代码打印出存储一个字符类型的长度。结果都是1吗?
实际的结果是4(32bit机器上)和1。字符常量’A’是char类型,由于sizeof(‘A’)作为printf的参数,函数的参数也算是表达式,所以发生了转型提升。
再看个实例:
char c1, c2;
...
c1 = c1 + c2;
这里c1和c2都需要先进行整形提升,即转化为int,然后两个int值相加,最后对和的结果进行裁剪。
参考:
- 《The C Programming Language中文版(第2版.新版)》
- 《C专家编程》