写在前面:本来因为一个朋友问我为什么可以给unsigned int赋值负数,我打算写一篇关于解释unsigned的文章。但是写的过程中发现有很多地方需要涉及其他的知识点,特别是关于整型提升和算数转换。所以就翻了一下之前看过的书,做了一个总结,感觉自己又学到了不少。由于我不是写教科书,有些用语和描述难免不够准确。另外,由于本人能力有限,有错之处在所难免,希望各位看到此文的朋友如果发现有什么错误请留言指正,或发邮件交流。
(电子邮箱:[email protected] ) ——转载请注明出处,lvyilong316
1. C的“类型提升”
1.1 什么是整形提升
char、short int或者int型位段(无论signed或unsigned)以及枚举类型,可以使用在需要int或者unsigned int的表达式中。如果int可以完整的表示源类型的所有值,那么该源类型的值就转换为int, 否则转换为unsigned int,这被称为整形提升。——《C专家编程》
注意:“整形提升”是先提升到int,而不管源数据是signed还是unsigned,而不是char 提升到int,unsigned char提升到unsigned int。
1.2 什么是类型提升
就是整型提升和float提升为double的总称。
1.3 什么时候需要类型提升
这个问题比较关键,从整形提升的概念可以看出,在【1】“char、short int或者int型位段(无论signed或unsigned)以及枚举类型出现在可以使用int或者unsigned int的表达式中”的时候,就会发生整形提升。与此类似,我们可以得到float到double的提升情况。但是ANSI C对类型提升有了弱化。ANSI C标准说明如下:
char c1,c2;
c1=c1+c2;
“类型提升”规则要求把每个变量提升为int的长度,然后两个int值执行加法运算,然后在对运算结果进行裁剪。如果两个char的加法运算结果不会发生溢出,那么实际执行时只要产生char类型的运算结果,可以省略类型提升。同样道理也适用于float到double的提升。
【2】另一个会发生隐式类型转换的地方就是参数传递。
在K&R C中,由于函数的参数也是表达式,所以也会发生类型提升。
在ANSI C中,如果使用了适当的函数原型,类型提升便不会发生,否则也会发生。 在被调用函数内部,提升后的参数被裁剪为原先声明的大小。
这就是为什么单个的printf()格式字符串%d能适用于几个不同类型,short,char或int,而不论实际传递的是上述类型的哪一个。函数从堆栈中取出的参数总是int类型,并在printf[1]或其他或其他被调用的函数里按统一格式处理。所以如果使用printf来打印比int长的类型,如long long,除非使用long long格式化限定符%ld,否则无法获取正确的值。因为在缺少更多信息的情况下,printf假定他处理的数据是int类型。
[1]即使一个原型位于printf()能及的范围内,注意它的原型以省略号结尾:
int printf(const char* format,...);
表示它是一个接收可变参数的函数,参数的信息(除第一个外)均未给出,此时一般的参数提升始终会发生。
总结:判断是否发生类型提升记住上述的【1】【2】即可。
1.4 类型提升实例分析
(1) printf(“%d”,sizeof(‘A’));//输出4
分析:sizeof()是一个表达式,字符’A’由char类型提升到int,所以打印出是4(int的大小),而不是1(char的大小)。
(2)char a,b;
printf ( " the size of the result of a+b :%d " ,sizeof( a+b) ); //输出4
* (3)char a;
printf ( "%d\n" ,sizeof(a)); //输出1
这个题要引起注意,为什么这里没有发生类型提升呢?我的理解是这样的:当传递的是字符常量的时候,sizeof是根据数值推断的,而当表达式使用到值的时候,就会进行进行类型提升(回忆整型提升定义,这里可以用int值代替)。但当参数是变量时,此时sizeof(a)实际等价于sizeof(char),并没有用到数值,所以输出1。
1.5 如何提升
我们把提升分为两个阶段——扩展和解释。
1.5.1 扩展
要想进行类型提升,很明显首先需要进行位扩展,比如char(1字节)提升到int(4字节)要在高位扩展3个字节。那么是如何扩展呢?答案是:有符号数按照有符号数的扩展规则(高位补符号位)扩展,无符号数按照无符号数的扩展规则(高位补0)扩展。
我们先看一个例子:
int main()
{
unsigned int a=0x12345678;
unsigned char b=(unsigned int)a;
char *c=(char*)&a;
printf("%04x %04x\n",b,*c );
return 0;
}
/*
*case1:unsigned int a=0x12345678;
*output:0078 0078
*
*case2:unsigned int a=0x123456a8;
*output:0078 ffffffa8;
*/
分析:
printf("%04x %04x\n",b,*c ); //实参为char,则要进行整形提升
*c是有符号的,做有符号扩展,高位补符号位。在case1中(78)符号位为0,因此提升后为:0x00000078,按宽度至少4位输出即0078。而在case2中(a8)符号位为1,因此提升后为:0xffffffa8,由于全是有效位,则忽略按4位宽度,输出:ffffffa8。 b提升成int,因为b是无符号的,所以做无符号扩展,高位补0;
1.5.2 解释
单单扩展完并没有完成任务,因为这涉及到程序该如何理解这些二进制位。这里我们再回到最开始的“整型提升”的定义——如果int可以完整的表示源类型的所有值,那么该源类型的值就转换为int, 否则转换为unsigned int。所以得到结论,优先解释为int。我们先看一个例子:
char a = -1;
unsigned char b = 1;
printf("%d", a > b);//输出0
分析:这个过程是这样的,首先对表达式a>b进行整型提升,a是-1,内存表示为0xff,由于a是有符号数,所以扩展为0xffffffff;b是1,内存表示为0x01,由于b是无符号的,所以扩展为0x00000001。接下来是解释,编译器都将其解释为int(注意,这里同为int,所以不发生算数转换),所以0xffffffff解释为-1,0x00000001解释为1.因为-1所以a>b不成立,输出0。
这个例子要和下面这个例子区别:
int a = -1;
unsigned int b = 1;
printf("%d", a > b);//输出1
这个没有发生类型提升(变量已经是int),而是算数转换,我们后面专题再介绍。
那什么时候解释为unsigned int呢?答案是有unsigned int参与的时候,比如下面这个例子:
char a= -1;
unsigned int b = -1;
printf("%d\n",a==b);//输出1
其实这个应该算是算数转换,我们可以把它看做一次整形提升+一次算数转换,首先将char提升为int,int和unsigned int的比较和运算就要涉及到算数转换了。所以我们可以简单地记住,整形提升都解释为int。