C中的隐式转换和溢出问题

有不对的地方请指出

隐式转换类型

C中的隐式转换大概有

  1. Integer Promotion
    将数据提升为int,long,long long型
  2. Usual Arithmetic Conversion
    把低优先级的数据类型转换为高优先级的数据类型
  3. 赋值时的类型转换
  4. 强制类型转换
    例如(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,281]。而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} 2N1个数。
我们先思考一下这样做可行吗,最高位牺牲了用来标志正负数,那么负数就都是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一站式学习》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值