隐式类型转换(Type Conversion)

1. 物理结构

  了解变量的隐式类型转换对变量存储的理解有极大的帮助。
  首先要说明的是,为什么C语言在编译时会存在Type Conversion这个bug(我之所以称其为bug,是因为如果我们忽略了它的存在,我们所存储的数据也许就不是我们所需要的,造成一系列的连锁反应和不可预知的结果)。
  计算机在执行两个数的算术运算时,操作数通常必须具有相同的大小(相同的位数)并以相同的方式存储。计算机可以直接将两个16位整数相加,但不能将一个16位整数和一个32位整数或一个32位整数和一个32位浮点数相加。这个怎么理解?在CPU中有一个被称为ALU(算术逻辑单元)的东西,当这个玩意儿被施加一定的控制信号,就可以分别实现加减乘除、与或非等算数和逻辑的操作。如果给ALU施加一个加法的信号,ALU就变成了一个加法器(下图是一个四位并行加法器),实现对两个二进制的bit位相加的操作。可以看到,加法器每个单元都对应着二进制两个bit位相加,如果一个数A是二位(如10),另一个数B是四位(1100),那么A4、A3最高位应该怎么处理呢,是高电平?还是低电平?亦或是悬空(TTL悬空相当于高电平)?显然这里给低电平(低电平用0表示,高电平用1表示)合适。可是一个有符号 char 型变量(-1 补码为 1111 1111)和 一个 有符号 short 型变量 (-1 补码为 1111 1111 1111 1111)相加, 若 char 和 short 不相交的位都给低电平,即 char 的 -1 变为默认变为(0000 0000 1111 1111)则结果为 (1111 1110 1111 1110),这是一个补码,转化为原码的结果是 (1000 0001 0000 0010),即 -258 。这个数与实际的计算结果 -2 大相径庭。这里要注意的是,char的 -1 转变是由物理的电路结构决定的,但是我们可以人为的设计电路使得char的高位填充仍然满足计算的正确。我们不讨论这个电路时如何实现的,只需关注在C语言语法的层面上会有哪些规定。
在这里插入图片描述

2. 代码剖析

我们先来看一段代码:
在这里插入图片描述
  定义 a 为无符号16bit 的 short型变量,并存上1111 1111 1111 1111 ;定义b为无符号char型,存上数值1(0000 0001)。当 a + b 发生后,按照上述所说的,b 会变为 0000 0000 0000 0001,则 a + b 的结果为0000 0000 0000 0000 显然这是c的结果。但当我们用u%打印 a + b 时发现,它的结果对应的二进制数为1 0000 0000 0000 0000,显然进位被存了下来。这说明 a + b 在运算的时候,似乎不仅仅是以 16bit 的方式运算,而是以一个更高的位数 —— int (8byte - 32bit),这一过程俗称整型提升(integer promotions),int 类型成为了C语言中运算的最小单元(这是C99的标准)。在C89中还是integral promotions:
integral promotions
以上截图来自《C Programming _ A Modern Approach》by K.N.KING.

3. 规则引入

3.1 变量类型的转换

  变量类型的转换意味着变量的存储空间发生了变化,当两个不同类型的变量(bit位数大于等于int32位)在发生相互作用时(加减乘除、比较、赋值等),遵循这样一个原则:
在这里插入图片描述
在这里插入图片描述

3.2 变量数值的转换

  正如第一节中引入的 -1 (shortchar) 在进行整型提升时,若要保证运算结果的正确,仍然需要规定一些原则。
  首先需要明确的是,变量在内存中以二进制补码的形式进行存储、运算,这样的好处是加法和减法的运算只需用加法器就可以实现了。补码的形式为:

int a = -10;
//二进制的原码,若为有符号(signed)负整数,则最高位符号位填1;有符号正整数,最高位为0;其余位按数的二进制表示
//无符号(unsighed)的整数,因为只能表示正值,每个bit位按照二进制位表示就行了。
// a 的原码为 1000 0000 0000 0000 0000 0000 0000 1010

// 二进制的反码,若为有符号(signed)负整数,符号位保持不变,其余位取反;有符号正整数,保持不变。
//无符号(unsighed)的整数保持不变。即正整数的反码和原码相同
// a 的反码为 1111 1111 1111 1111 1111 1111 1111 0101

//二进制的补码,若为有符号(signed)负整数,符号位保持不变,其余位加 1 ;同样正整数的补码保持不变。
//总结就是正整数的原码、反码、补码相同。
// a 的补码为 1111 1111 1111 1111 1111 1111 1111 0110

  原码到补码的运算遵循上诉规则(参考操作符的细节(!~)加深理解),补码到原码的运算仍然可以采用相同的方式(补码取反 + 1 ,或者补码 -1 再取反,unsigned 符号位不变)
  对于int类型变量有32bit,可以表达232种可能,那么对于unsigned int 可以表达的数值范围就是0 ~ 232-1 ;对于signed int 为-231 ~ 231-1。unsigned int 在存满了数值 1 时 即0xFFFFFFFF ,再加 1 就得到了 0x00000000,另外多了一个进位(这个进位也许会把某一个寄存器的一个bit置位);signed int 的 -231 原码为0x8000000,补码也为0x8000000,似乎成了首位相连的一个契机。
其他类型也可作类似的推断。

有符号数进行整形提升时,正数高位补0,负数高位补1;

无符号数进行整形提升时高位补0(当作正整数处理)
例如:
char a = -10//1000 1010  求补> 1111 0110 
int b = a;  // a 被提升为 1111 1111 1111 1111 1111 1111 1111 0110  > b = -10

4. 其他

  隐式类型转换另一个现象是,当一个int 型(float也一样)的变量被赋值一个double型的数据时,则会发生截断。这在 C语言中的常量和变量(一) 2.2 中 提了一下。

int i;
i = 3.14//此时i为3

避免隐式类型转换的一种方式是使用强制类型转换(Type casting),这让一些必要的精度损失能够人为的控制,而不是由编译器来完成。

参考资料

[1]: 《Digital Fundamentals》.ELEVENTH EDITION . Thomas L. Floyd
[2]: 《C Programming _ A Modern Approach》.SECOND EDITION. K.N.KING.

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值