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: 以上截图来自《C Programming _ A Modern Approach》by K.N.KING.
3. 规则引入
3.1 变量类型的转换
??变量类型的转换意味着变量的存储空间发生了变化,当两个不同类型的变量(bit位数大于等于int32位)在发生相互作用时(加减乘除、比较、赋值等),遵循这样一个原则:
3.2 变量数值的转换
??正如第一节中引入的 -1 (short 和char ) 在进行整型提升时,若要保证运算结果的正确,仍然需要规定一些原则。 ??首先需要明确的是,变量在内存中以二进制补码的形式进行存储、运算,这样的好处是加法和减法的运算只需用加法器就可以实现了。补码的形式为:
int a = -10;
??原码到补码的运算遵循上诉规则(参考操作符的细节(!~)加深理解),补码到原码的运算仍然可以采用相同的方式(补码取反 + 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;
int b = a;
4. 其他
??隐式类型转换另一个现象是,当一个int 型(float也一样)的变量被赋值一个double型的数据时,则会发生截断。这在 C语言中的常量和变量(一) 2.2 中 提了一下。
int i;
i = 3.14;
避免隐式类型转换的一种方式是使用强制类型转换(Type casting ),这让一些必要的精度损失能够人为的控制,而不是由编译器来完成。
参考资料
[1]: 《Digital Fundamentals》.ELEVENTH EDITION . Thomas L. Floyd [2]: 《C Programming _ A Modern Approach》.SECOND EDITION. K.N.KING.
|