在C语言程序开发中,程序员可以在定义数组时对其初始化,并且允许只对一部分元素初始化,未显式给定初值的元素将被置零,例如:
int a[5] = {1, 2};
这行C语言代码执行完毕后,数组 a 中的元素将是 1,2,0,0,0。这是C语言数组的基本知识点。但是遗憾的是,从我昨天在圈子里分享的一个面试题来看,仍有部分C语言程序员认为未显式给定初值的数组元素值是未定义的。下面是国外某嵌入式公司的面试题,有人在 StackOverflow 上提问,感觉比较有意思:
![b90f531afa601ede016ba734d2e379cd.png](https://i-blog.csdnimg.cn/blog_migrate/0930c1fae1671e2ff13288ce029a2954.jpeg)
国外某嵌入式公司的面试题
题外话
似乎我每次分享一些面试题都有人觉得我是“孔乙己”,纠结这些面试题没有意义。但是真的没有意义吗?首先要明确的是,分析面试题并不是推崇面试题中的C语言代码写法,而是讨论其背后隐藏的基础知识。例如,如果读者不能明白C语言数组初始化方面的性质,是无法得出这个题目的正确答案的。
事实上,很多公司招聘时,都有一些面试题或者笔试题看起来很怪异,很不符合标准的开发规范,于是有些程序员就认为做这样的面试题是完全没有意义的。但是其实呢,这些题目可能来自公司内部的某个项目的某次重大 bug,短短的面试期间完整的描述该 bug 不太可能,只好将其抽象成一个看起来很“冷僻”的面试题,考察求职者。
![5039065204a3c459307584b7a91f2280.png](https://i-blog.csdnimg.cn/blog_migrate/015180102f2ee9ddb7819350a4372f8e.jpeg)
考察求职者
如果求职者基础不扎实,以后可能还会犯同样的错误,这是不允许的。因此,对于我们求职者来说,平时拿到这些面试题,首先想到的应该是它背后隐藏哪些知识点,查漏补缺才是正道。
分析
有很多乐于动手的读者看到题目后,立即在自己的设备上编写相关C语言代码并编译执行,但是似乎得到了两种答案,如下所示,这是怎么回事呢?
1, 0, 0, 0, 0// 或者1, 0, 1, 0, 0
其实,本题主要考察两个知识点,一是前面提到的C语言数组初始化,再就是C语言语句“序列点(sequence point)”的概念了。(关于“序列点”,可以参考我之前的文章:《这段C语言代码虽然简单,但其隐含的问题,很多程序员都容易忽略》)
![fd98247073c5f1e06fa63446911e0bdb.png](https://i-blog.csdnimg.cn/blog_migrate/b39191aae1413539c3c6d457814a2d3a.jpeg)
这是怎么回事呢?
出现两种结果的原因是,int a[5]={a[2]=1}; 其实C语言标准并无明确定义(至少在C99中如此)。就本题而言,唯一明确的是 a[0] 会被设为 1,其他的一切将不能确定结果。
C语言程序员应该对C标准有所了解,那本题恰好就是一个契机。
首先,有读者质疑int a[5]={a[2]=1}; 在C语言中是否合法。所以先查阅相关标准:数组 a 是一个局部变量,因为在标准中提到:
6.7.8 InitializationAll 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 必定没有静态存储空间,也即只能是局部变量。
![73f835d18e56e42043e049f8ae38793d.png](https://i-blog.csdnimg.cn/blog_migrate/74f5dbc3fa5aa682824eff81472a5e61.jpeg)
a 必定没有静态存储空间
局部变量 a 的作用域也包含它自己的初始化,因此 int a[5]={a[2]=1}; 的写法在C语言中应该是合法的,这一点可参照标准:
6.2.4 Storage durations of objectsFor 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 InitializationThe 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)”也是不能确定的。
![88d57262a0fe7e94d86dd1f61ef66f08.png](https://i-blog.csdnimg.cn/blog_migrate/b8688012fa507fb5df7a635d8c1f49af.jpeg)
为什么这段C语言代码会产生两个结果?
稍稍总结一下,应该就能明白为什么这段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标准。在此期间,相信我们的技术不知不觉就提升了。
![6f4de92314f27e203fe8a471cbb172a5.png](https://i-blog.csdnimg.cn/blog_migrate/beeea78752bfd417cb31e82ffdd75e24.jpeg)
点个赞再走吧
欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。