有不对的地方请指出
隐式转换类型
C中的隐式转换大概有
- Integer Promotion
将数据提升为int,long,long long型 - Usual Arithmetic Conversion
把低优先级的数据类型转换为高优先级的数据类型 - 赋值时的类型转换
- 强制类型转换
例如(double)3+i
,先把3转换为3.0
下面举几个Integer Promotion的例子,再延伸到溢出问题
Integer Promotion
先判断下面几个程序的运行结果,如果你都能得出正确答案,那这篇文章就不用看了😄
提示:
unsigned char 的范围是0~255
unsigned short 的范围是0~65535
short的范围是-32768~32767
unsigned char c1=256;
printf("%d\n",c1);
输出结果是0,因为unsigned char 范围是0~255,所以256是0,257是1
c规定一个char就是一个字节,即8bit,因为是无符号的,所以最高位也可以参与表示,所以有8个bit能表示数字,那么能表示数的范围就是
[
0
,
2
8
−
1
]
[0,2^8-1]
[0,28−1]。而255用二进制表示是11111111,即8个bit全为1。那么256按理来说是10000000,也就是1111111+1,但是由于char只有8个bit,最高位的1被舍去了,因此此时c1的值就是0
unsigned char c1=255,c2=1;
printf("%d\n",c1+c2);
输出结果是:256,不是说unsigned char 的范围是0~255吗?这里怎么不是0?
原因就是在c1+c2
这里发生了Integer Promotion,注意到printf是输出一个整数,那么这行代码其实和下面的一样,也就是C会定义一个临时变量存储c1+c2的结果,然后再输出n
int n=c1+c2;
printf("%d\n",n);
由于c1和c2都是unsigned char型,而n是int型,所以C编译器会做Integer Promotion
过程如下
3. 先把c1和c2的值提升为int类型
4. 相加c1,c2
5. 把相加的结果赋值给n
那么最后输出的结果是256,256是在int的范围里面的,所以这里并没有发生溢出
那么读者可能有问题了,我这里是两个unsigned char 相加,不论如何范围都在int能表示的范围里面,如果我的值其实超过了int的范围,那这不是还会溢出?
碰到这种情况编译器就会把int类型提升为unsigned int类型(%d需要改为%u),甚至unsigned long long类型(%d需要改为%lld),直到足以能够表示这些值
Integer Promotion有很多规则,这里只是略微谈一下,不展开了
unsigned char c1=255,c2=1,c3=c1+c2;
printf("%d\n",c3);
输出结果是0,这里又发生溢出了,怎么和第二种情况不一样?
细心一点就会发现,printf输出的仍然是整数没错,输出c3仍然存在着把c3提升为int类型的过程,但是这里c3的值其实就是0了,因为c3=c1+c2
这里就已经溢出了,即c3为00000000
我们可以用gdb看一下
可以看到c3此时的值就是0了
unsigned short i=65536;
printf("%d\n",i);
输出结果0,也是溢出了
short的范围是-32768~32767
short i=32767+1;
printf("%d\n",i);
输出结果-32768,嗯?怎么会这样
其实这涉及到计算机如何表示负数的问题。
我们先看32767时候i的bit表示为0111 1111 1111 1111
那么+1之后,值为32768,bit表示为1000 0000 0000 0000,很正常对吧
问题就出在这个最高位1上了。
由于计算机要表示负数,要有额外的标志,通常我们最高位取0表示该数是正数,最高位取1表示该数是负数。
由于最高位用来表示正负数了,那么计算机能用到的就是剩下的15位了,同样的如果是N个bit用来表示有符号数,那么它能表示的数一共是
2
N
−
1
2^{N-1}
2N−1个数。
我们先思考一下这样做可行吗,最高位牺牲了用来标志正负数,那么负数就都是1…这样的数了,看起来不错,很符合人的逻辑,但是对计算机来说有一个很大的麻烦,既然100000…000是0,那么00000…00000也是0了,同一个数怎么会有两个表示法?而且用这种方法表示负数,正负数之间加减的逻辑会很复杂。用最高位1来直接表示负数是不行的。
后面又有了另一种表示负数的方法:假如一个数的最高位是1,那么对它除了最高位全部取反,然后再加1,这时的值就是该数表示负数的值,即先取反码再加一 ,此时得到的码就成为补码
正数的补码是原码本身
先用8bit演示一下
9的bit值为:0000 1001对吧
逻辑上-9的bit值应该是1000 1001,这个码称为-9的原码
对1000 1001,取反码得1111 0110(按位取反,但此时最高位不变),再加1,得到1111 0111,这个1111 0111称为1000 1001的补码,在计算机中-9就是用1111 0111来表示的。
或者由9的bit值为0000 1001,对它取反码,得到1111 0110,再加1得到1111 0111,同样也是表示-9
9-9=0,而0000 1001+1111 0111(补码)确实等于0了。
好了,回到问题本身,32767=0111 1111 1111 1111,那么32767+1=32768=1000 0000 0000 0000,对我们来说是32768没错,但此时最高位是1,对计算机来说它是一个负数,也就是1000 0000 0000 0000是某个数的补码,我们逆运算回来求原码,发现还是1000 0000 0000 0000=32768,所以1000 0000 0000 0000在计算机中表示的是-32768.
再整理一下,1000 0000 0000 0000是一个负数,改该数的绝对值是取反再+1,为1000 0000 0000 0000=32768,即该数是-32768
(写得有点乱)
搞懂了上面的例子,再看第六题
short i =65535;
printf("%d\n",i);
输出结果是:-1,原因是65535=0xffff=1111 1111 1111 1111,最高位还是1,那么按照同样的方法,该数的绝对值是取反再加1,即0000 0000 0000 0001=1,那么这个负数值是-1。
参考《LinuxC一站式学习》