前两天,我在我的圈子里发了一个小问题,相关的C语言代码如下,这段程序会输出什么呢?
#include <stdio.h>
int main()
{
signed char a = (unsigned char)-1;
unsigned char b = a;
int c = a;
int d = b;
printf("%d %d %d %d\n", a, b, c, d);
return 0;
}
题外话
在分析这个问题之前,先说些题外话。有程序员认为研究这样的代码没有意义,无异于孔乙己的“茴”字有几种写法。
这个问题其实并不是我空想出来的。
最近,我的一个同事被他的C语言程序 bug 困扰了好几天,始终无法找到问题究竟出在哪里,于是找我,我看到他的代码居然混用无符号变量和有符号变量,于是就提醒他注意这个方面,后来发现果然是这个原因。他的问题涉及到比较复杂的项目,完整的复述一遍不太现实,于是我把他的问题精简一下,就构成了上述C语言代码段。
事实上,很多公司招聘时,都有一些面试题或者笔试题看起来很怪异,很不符合标准的开发规范,于是有些程序员就认为做这样的面试题是完全没有意义的,甚至觉得做这些题目是一种侮辱。
其实换个角度想想,这些题目很能考察一个人的基本功,它们很可能来自公司内部的某个项目的某次重大 bug。C语言是一门极其重视基本功的编程语言,这些题目很能查漏补缺。
分析
现在来考虑上面这段C语言代码,我们编译并执行它,得到了下面的输出:
# gcc t.c
# ./a.out
-1 255 -1 255
C语言程序的输出出乎了一些朋友的预料,-1 容易理解,255 是怎么回事呢?
首先要明白的是,在计算机中,整数通常采取补码的形式存储。负数的补码等于其反码+1,负数的反码符号位不变,数值为按位取反。对于 signed char 型变量,大部分C语言编译器都是由 8 个 bit 组成的,最高一个 bit 通常表示符号位。
所以对于 -1,其原码原本是 0b10000001,但是计算机内部存储该数值时,是以补码形式存储的。-1 的补码等于反码+1,也即 0b11111110 +1 = 0b11111111 = 0xff。
到这里就清楚了,变量 b 在内存里的 8 个 bit 都是 1,它是一个 unsigned char 型的变量,最高 bit 也表示数值,也即 b 等于 255。
现在再来分析变量 c 和变量 d 的值,它俩都是有符号型的 int 型。按理说,a 和 b 在内存中的布局是一样的,都是 8 个 bit 的 1,为什么传递给 c 和 d 就不一样了呢?
其实C语言在处理 c = a; 和 d = b; 这两句赋值语句时,有一个过程没有显式的表现出来,即“整形提升”。以 c=a; 为例,因为 c 和 a 的数据类型不同,所以C语言在处理赋值时,为了不丢失精度,会将 a 中的数值也强制转换为 int 型。
a 中的数值是 -1,提升为 int 型后依然是 -1,而不是 0x000000ff(255,这里假设 int 类型占用 4 字节内存空间)。至于变量 d 的值,就更简单了,就是简单的赋值而已。
小结
本节讨论的问题虽然很简单,但是仍然有很多人做错,这其中也包含我工作多年的同事。C语言是一门极其重视基本功的编程语言,事实上,本节涉及的知识点非常基础,无非就是原码补码,以及整型提升的相关知识。