在C语言程序开发中,程序员可以在定义数组时对其初始化,并且允许只对一部分元素初始化,未显式给定初值的元素将被置零,例如:
int a[5] = {1, 2};
这行C语言代码执行完毕后,数组 a 中的元素将是 1,2,0,0,0。这是C语言数组的基本知识点。但是遗憾的是,从我昨天在圈子里分享的一个面试题来看,仍有部分C语言程序员认为未显式给定初值的数组元素值是未定义的。下面是国外某嵌入式公司的面试题,有人在 StackOverflow 上提问,感觉比较有意思:
题外话
似乎我每次分享一些面试题都有人觉得我是“孔乙己”,纠结这些面试题没有意义。但是真的没有意义吗?首先要明确的是,分析面试题并不是推崇面试题中的C语言代码写法,而是讨论其背后隐藏的基础知识。例如,如果读者不能明白C语言数组初始化方面的性质,是无法得出这个题目的正确答案的。
事实上,很多公司招聘时,都有一些面试题或者笔试题看起来很怪异,很不符合标准的开发规范,于是有些程序员就认为做这样的面试题是完全没有意义的。但是其实呢,这些题目可能来自公司内部的某个项目的某次重大 bug,短短的面试期间完整的描述该 bug 不太可能,只好将其抽象成一个看起来很“冷僻”的面试题,考察求职者。
如果求职者基础不扎实,以后可能还会犯同样的错误,这是不允许的。因此,对于我们求职者来说,平时拿到这些面试题,首先想到的应该是它背后隐藏哪些知识点,查漏补缺才是正道。
分析
有很多乐于动手的读者看到题目后,立即在自己的设备上编写相关C语言代码并编译执行,但是似乎得到了两种答案,如下所示,这是怎么回事呢?
1, 0, 0, 0, 0
// 或者
1, 0, 1, 0, 0
其实,本题主要考察两个知识点,一是前面提到的C语言数组初始化,再就是C语言语句“序列点(sequence point)”的概念了。(关于“序列点”,可以参考我之前的文章:《》)
出现两种结果的原因是,int a[5]={a[2]=1}; 其实C语言标准并无明确定义(至少在C99中如此)。就本题而言,唯一明确的是 a[0] 会被设为 1,其他的一切将不能确定结果。
C语言程序员应该对C标准有所了解,那本题恰好就是一个契机。
首先,有读者质疑int a[5]={a[2]=1}; 在C语言中是否合法。所以先查阅相关标准:数组 a 是一个局部变量,因为在标准中提到:
6.7.8 Initialization
All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals.
意思是,所有的拥有静态存储的初始化表达式必须使用常量表达式初始化,本题中数组 a 使用 a[2]=1 初始化,因此 a 必定没有静态存储空间,也即只能是局部变量。
局部变量 a 的作用域也包含它自己的初始化,因此 int a[5]={a[2]=1}; 的写法在C语言中应该是合法的,这一点可参照标准:
6.2.4 Storage durations of objects
For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends
但是,虽然这种写法是合法的,上述C语言代码依然不能确定数组 a 的结果,这在标准中也是有描述的,请看:
6.7.9 Initialization
The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified.
大意是,初始化列表表达式的计算顺序是不确定的,因此期间发生的“副作用(side effect)”也是不能确定的。
稍稍总结一下,应该就能明白为什么这段C语言代码会产生两个结果了。事实上,C语言表达式:
int a[5] = {a[2]=1};
就相当于 int a[5]={1}; 和 a[2]=1; 两个表达式的组合,表达式 int a[5] = {1},将使得数组 a 中元素被初始化为
1, 0, 0, 0, 0
而 a[2] =1 则会将数组 a 的 2 号元素设为 1,最终得到结果
1, 0, 1, 0, 0
但是究竟最后会产生哪个结果呢?遗憾的是,按照上面的讨论, int a[5]={1}; 和 a[2]=1; 两个表达式的副作用产生顺序是不确定的,也就是说,我们不能确定 a[2]=1; 究竟是在 int a[5]={1}; 之前,还是之后执行,这就可能导致出现两个结果,两个结果都不能认为是错的。
小结
正如前面所说,“怪异的”面试题也能够帮助我们查漏补缺,为了解决这个面试题,我们甚至还翻阅了一些C标准。在此期间,相信我们的技术不知不觉就提升了。