C 中数组初始化的困惑

标签 c arrays initialization language-lawyer

在C语言中,如果像这样初始化一个数组:

int a[5] = {1,2};

然后数组中所有未显式初始化的元素都将隐式初始化为零。

但是,如果我像这样初始化一个数组:

int a[5]={a[2]=1};

printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);

输出:

1 0 1 0 0

我不明白,为什么a[0]打印1而不是0?这是未定义的行为吗?

注意:这个问题是在采访中提出的。

最佳答案

TL;DR:我认为 int a[5]={a[2]=1}; 的行为没有明确定义,至少在 C99 中是这样。

有趣的是,对我来说唯一有意义的是您所询问的部分:a[0] 设置为 1 因为赋值运算符返回分配的值。其他一切都不清楚。

如果代码是 int a[5] = { [2] = 1 },一切都会很简单:这是一个指定的初始化器设置 a[2] code> 为 1,其他所有内容为 0。但是对于 { a[2] = 1 } 我们有一个包含赋值表达式的非指定初始化器,我们就掉进了兔子洞。

<小时/>

这是我迄今为止发现的内容:

  • a 必须是局部变量。

    6.7.8 Initialization

    1. All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals.

    a[2] = 1 不是常量表达式,因此 a 必须具有自动存储功能。

  • a 在其自己的初始化范围内。

    6.2.1 Scopes of identifiers

    1. Structure, union, and enumeration tags have scope that begins just after the appearance of the tag in a type specifier that declares the tag. Each enumeration constant has scope that begins just after the appearance of its defining enumerator in an enumerator list. Any other identifier has scope that begins just after the completion of its declarator.

    声明符是a[5],因此变量在它们自己的初始化范围内。

  • a 在其自身的初始化中处于事件状态。

    6.2.4 Storage durations of objects

    1. An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic storage duration.

    2. 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 in any way. (Entering an enclosed block or calling a function suspends, but does not end, execution of the current block.) If the block is entered recursively, a new instance of the object is created each time. The initial value of the object is indeterminate. If an initialization is specified for the object, it is performed each time the declaration is reached in the execution of the block; otherwise, the value becomes indeterminate each time the declaration is reached.

  • a[2]=1之后有一个序列点。

    6.8 Statements and blocks

    1. A full expression is an expression that is not part of another expression or of a declarator. Each of the following is a full expression: an initializer; the expression in an expression statement; the controlling expression of a selection statement (if or switch); the controlling expression of a while or do statement; each of the (optional) expressions of a for statement; the (optional) expression in a return statement. The end of a full expression is a sequence point.

    请注意,例如在 int foo[] = { 1, 2, 3 } 中,{ 1, 2, 3 } 部分是一个用大括号括起来的初始化器列表,每个初始化器都有一个其后的序列点。

  • 初始化按初始值设定项列表顺序执行。

    6.7.8 Initialization

    1. Each brace-enclosed initializer list has an associated current object. When no designations are present, subobjects of the current object are initialized in order according to the type of the current object: array elements in increasing subscript order, structure members in declaration order, and the first named member of a union. [...]

     

    1. The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject; all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
  • 但是,初始化表达式不一定按顺序求值。

    6.7.8 Initialization

    1. The order in which any side effects occur among the initialization list expressions is unspecified.
<小时/>

但是,这仍然留下了一些悬而未决的问题:

  • 序列点是否相关?基本规则是:

    6.5 Expressions

    1. Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.

    a[2] = 1 是一个表达式,但初始化不是。

    这与附件 J 略有矛盾:

    J.2 Undefined behavior

    • Between two sequence points, an object is modified more than once, or is modified and the prior value is read other than to determine the value to be stored (6.5).

    附件 J 表示任何修改都有效,而不仅仅是表达式的修改。但鉴于附件不规范,我们或许可以忽略它。

  • 子对象初始化如何相对于初始值设定项表达式排序?是否首先评估所有初始值设定项(按某种顺序),然​​后用结果初始化子对象(按初始值设定项列表顺序)?或者它们可以交错吗?

<小时/>

我认为int a[5] = { a[2] = 1 }执行如下:

  1. a 的存储空间是在进入其包含 block 时分配的。目前内容尚未确定。
  2. 执行(唯一的)初始值设定项 (a[2] = 1),后跟一个序列点。这会将 1 存储在 a[2] 中并返回 1
  3. 1 用于初始化 a[0](第一个初始化器初始化第一个子对象)。

但是这里事情变得模糊,因为其余元素 (a[1], a[2], a[3], a[4]) 应该被初始化为 0,但不清楚何时:它是否发生在 a[2] = 1 之前评价?如果是这样,a[2] = 1 将“获胜”并覆盖 a[2],但该赋值是否会具有未定义的行为,因为零之间没有序列点初始化和赋值表达式?序列点是否相关(见上文)?或者在评估所有初始化器之后是否会发生零初始化?如果是这样,a[2] 最终应该是 0

因为 C 标准没有明确定义这里发生的情况,所以我相信该行为是未定义的(由于遗漏)。

关于C 中数组初始化的困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52307474/

相关文章:

c - 定义结构类型的数组

c - Qt 原子操作使用我的 MIPS 24k 核心中不存在的条件代码寄存器

c# - 如何对字符串数组进行排序?

c++ - 多次调用初始化函数时的最佳实践?

c++ - 是否应该在 C++ 头文件中初始化 const 静态变量?

c - 将自定义 glibc 与 bazel 结合使用

mysql - 在单选按钮中插入数组。 0 代表错误答案,1 代表正确答案

java - 是什么导致了 java.lang.ArrayIndexOutOfBoundsException 以及如何防止它?

ios - 我如何以及在何处初始化 Xcode 5 中的全局 NSMutableArray

c - C中的中止陷阱6错误