因为在在编码为补码的情形下,类型提升有两种情况:
1. 符号扩展:对于有符号数,扩展存储位数的方法。在新的高位字节使用当前最高有效位即符号位的值进行填充。
2. 零扩展:对于无符号数,扩展存储位数的方法。在新的高位直接填0.
对于这个例子来说。*p是无符号数,所以填充的是0,即为0x000000ff。而*p1是有符号数,所以填充的是1,即为0xffffffff。
因此,从char型到unsigned int,是对有符号数的提升,因此用的是符号扩展,oxff被扩展为oxffffffff;而从unsigned char型到unsigned int型,是对无符号数的扩展,使用零扩展,oxff被扩展为ox000000ff,而填充的这些零是不会被打印出来的。
如果说这样教科书式的概念不容易理解。还有这样一种理解方式,也许不一定准确,但更容易理解。
对于这里的类型提升,整个步骤可以这样理解:
1. %x要求参数为无符号整数,需要参数为4个字节;
2. *p, *p1为(unsigned) char型,只占1个字节;
3. 因为参数的类型不符,需要扩展;
4. 定位需要扩展到4个字节;
5. 那么就需要填充增加的3个字节;
6. 这3个字节需要什么值?这里就需要上面所需要的概念了。针对有符号数和无符号数,进行不同值的填充。
一、从一道微软面试题说起
微软面试题:
-
unsigned int i=3;
-
cout<<i*-1;
问输出是多少?
如果忽略了有符号数到无符号数的隐式转换,答案就是-3,但是微软面试题没那么简单。
运行一下上面的程序,发现结果是4294967293。其实是因为i是unsigned int类型,而-1是int类型,一个无符号,一个有符号,编译时会发生强制类型转换,统一将有符号数转化为无符号数,然后再进行运算。
解析:
首先,要明确三点:
1、unsigned int和int类型的数据大小均为4字节,即32位。(无论是32位字长机器还是64位字长机器)2、机器一般都是用补码表示有符号数。3、有符号数到无符号数的转换规则:位值保持不变,只改变解释这些位值的方式。
回到本题中,根据上面三点,下面分几步分析:
#1:-1的机器码表示
根据第1点,-1的机器码为:0xFFFFFFFF,即 1111...1111(32个1)
知识补充:为什么 1111...1111 (32个1)表示-1?
根据上面第2点,用补码表示有符合数。1111...1111 (32个1)是补码,它计算得到-1的过程如下:
1*(-2^31)+1*2^30+1*2^29+...+1*2^1+1*2^0=-1
注意,因为是补码,所以规定最高位的权重是负的,其他位的权重是正的,这样计算出来的数就表示有符号数。
int可以表示的范围是 -2^31~2^31-1,十六进制0x80000000~0x7fffffff,十进制-2147483648~2147483647
#2:-1转化为无符号数是多少?
根据第3点,-1转化为无符号数时,保持位值不变,即转化后机器码仍然是0xFFFFFFFF,但是此时它表示无符号数,各个位置的权重均是正的,因此转化结果是1*2^31+1*2^30+...+1*2^0=4294967295
#3:运算
4294967295*3=12884901885,而unsigned int的表示范围为0~2^32-1,12884901885超出了该范围。
C语言中规定这种情况要采取模运算,即将无符号乘积模2^32(取余)
12884901885 mod 2^32 = 4294967293,相当于取乘积的低32位,将其写成十六进制形式会更加明显:0x2FFFFFFFD mod 0xFFFFFFFF = 0xFFFFFFFD
下面再举《深入理解计算机系统》中的两个例子
二、计算数组中元素的和,length为0引起的程序错误
代码:
[cpp] view plain copy
-
float array_sum(float a[],unsigned length)
-
{
-
int i;
-
float sum=0;
-
for(i=0;i<=length-1;i++)
-
sum+=a[i];
-
return sum;
-
}
注意函数中参数length被声明为unsigned类型,数组长度作为无符号数传递,看起来是很理所当然的做法,但是它却会导致意外的结果。
当我们调用array_sum(a,0)时,length=0,在计算length-1时,因为length是unsigned,所以(0-1)的结果也应该是unsigned,因此结果不是-1,而是Umax(Umax是unsigned int可以表示的最大数2^32-1)。这将导致运行错误,因为访问了数组的非法元素。
可以将i<=length-1改为i<length,或者length声明为int类型
三、用字符串库函数strlen判断字符串str1是否比str2长
代码:
[cpp] view plain copy
-
/*strlen函数原型*/
-
size_t strlen(const char *s);
-
-
/*长度比较函数*/
-
int strlonger(char *str1,char *str2)
-
{
-
return strlen(str1)-strlen(str2)>0;
-
}
注意,strlen函数原型的返回类型是size_t,在stdio.h头文件中该类型是被定义成unsigned int类型,是无符号类型。
因此,strlen(str1)-strlen(str2)的结果恒大于0,无论str1比str2长还是短,结果均返回1。
可以将return strlen(str1)-strlen(str2)>0 改为return strlen(str1)>strlen(str2);