关于 C 中数组初始化的困惑 [英] Confusion about array initialization in C

查看:26
本文介绍了关于 C 中数组初始化的困惑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In C language, if initialize an array like this:

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

那么所有未显式初始化的数组元素将被隐式初始化为零.

then all the elements of the array that are not initialized explicitly will be initialized implicitly with zeros.

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

But, if I initialize an array like this:

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?这是未定义的行为吗?

I don't understand, why does a[0] print 1 instead of 0? Is it undefined behaviour?

注意:这个问题是在一次采访中被问到的.

Note: This question was asked in an interview.

推荐答案

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

TL;DR: I don't think the behavior of int a[5]={a[2]=1}; is well defined, at least in C99.

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

The funny part is that the only bit that makes sense to me is the part you're asking about: a[0] is set to 1 because the assignment operator returns the value that was assigned. It's everything else that's unclear.

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

If the code had been int a[5] = { [2] = 1 }, everything would've been easy: That's a designated initializer setting a[2] to 1 and everything else to 0. But with { a[2] = 1 } we have a non-designated initializer containing an assignment expression, and we fall down a rabbit hole.

这是我目前发现的:

  • a 必须是局部变量.

6.7.8 初始化

  1. 具有静态存储持续时间的对象的初始值设定项中的所有表达式都应为常量表达式或字符串文字.

a[2] = 1 不是常量表达式,所以 a 必须有自动存储.

a[2] = 1 is not a constant expression, so a must have automatic storage.

a 在其自身的初始化范围内.

a is in scope in its own initialization.

6.2.1 标识符的范围

  1. 结构、联合和枚举标签的作用域在出现声明标签的类型说明符中的标签.每个枚举常量都有作用域在枚举器列表中其定义的枚举器出现之后开始.任何其他标识符的范围刚好在其声明符完成之后开始.

声明符是 a[5],所以变量在它们自己的初始化范围内.

The declarator is a[5], so variables are in scope in their own initialization.

a 在它自己的初始化中是活的.

a is alive in its own initialization.

6.2.4 对象的存储期限

  1. 一个对象,其标识符被声明为没有链接且没有存储类说明符 static 具有自动存储持续时间.

对于这样一个没有变长数组类型的对象,它的生命周期延长从进入与其关联的块直到该块的执行结束反正.(进入封闭块或调用函数会挂起,但不会结束,执行当前块.)如果递归进入该块,则该块的新实例每次都会创建对象.对象的初始值是不确定的.如果为对象指定了初始化,每次声明时都会执行在块的执行中到达;否则,每个值都变得不确定到达声明的时间.

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 语句和块

    1. 完整表达式 是不属于另一个表达式或声明符的表达式.以下每个都是一个完整的表达式:一个初始化器;表达式中的表达式陈述;选择语句的控制表达式(ifswitch);这whiledo 语句的控制表达式;每个(可选)表达式for 语句;return 语句中的(可选)表达式.一个完整的结束表达式是一个序列点.
    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 } 部分是一个括号括起来的初始化器列表,每个初始化器都有一个其后的序列点.

    Note that e.g. in int foo[] = { 1, 2, 3 } the { 1, 2, 3 } part is a brace-enclosed list of initializers, each of which has a sequence point after it.

    初始化按照初始化列表的顺序进行.

    Initialization is performed in initializer list order.

    6.7.8 初始化

    1. 每个花括号括起来的初始化列表都有一个关联的当前对象.当没有存在指定,当前对象的子对象按照顺序初始化到当前对象的类型:按递增下标顺序的数组元素,按声明顺序的结构成员,以及联合的第一个命名成员.[...]
    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. 初始化应按照初始化列表的顺序进行,每个初始化为一个特定子对象覆盖任何先前列出的相同子对象的初始化程序;全部未显式初始化的子对象应隐式初始化具有静态存储持续时间的对象.

  • 但是,初始化表达式不一定按顺序求值.

  • However, initializer expressions are not necessarily evaluated in order.

    6.7.8 初始化

    1. 在初始化列表表达式中出现任何副作用的顺序是未指明.

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

    However, that still leaves some questions unanswered:

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

    • Are sequence points even relevant? The basic rule is:

    6.5 表达式

    1. 在上一个和下一个序列点之间,一个对象应该有它的存储值最多修改一次通过对表达式的评估.此外,先验值应只读以确定要存储的值.
    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 是一个表达式,但初始化不是.

    a[2] = 1 is an expression, but initialization is not.

    这与附件 J 略有矛盾:

    This is slightly contradicted by Annex J:

    J.2 未定义行为

    • 在两个序列点之间,一个对象被多次修改,或者被修改读取先验值,而不是确定要存储的值 (6.5).

    Annex J 说任何修改都算数,而不仅仅是表达式的修改.但鉴于附件是非规范性的,我们可能可以忽略这一点.

    Annex J says any modification counts, not just modifications by expressions. But given that annexes are non-normative, we can probably ignore that.

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

    How are the subobject initializations sequenced with respect to initializer expressions? Are all initializers evaluated first (in some order), then the subobjects are initialized with the results (in initializer list order)? Or can they be interleaved?

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

    1. a 的存储在其包含块被输入时被分配.内容在这一点上是不确定的.
    2. 执行(唯一的)初始化程序(a[2] = 1),然后是一个序列点.这将 1 存储在 a[2] 中并返回 1.
    3. 那个1用于初始化a[0](第一个初始化器初始化第一个子对象).
    1. Storage for a is allocated when its containing block is entered. The contents are indeterminate at this point.
    2. The (only) initializer is executed (a[2] = 1), followed by a sequence point. This stores 1 in a[2] and returns 1.
    3. That 1 is used to initialize a[0] (the first initializer initializes the first subobject).

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

    But here things get fuzzy because the remaining elements (a[1], a[2], a[3], a[4]) are supposed to be initialized to 0, but it's not clear when: Does it happen before a[2] = 1 is evaluated? If so, a[2] = 1 would "win" and overwrite a[2], but would that assignment have undefined behavior because there is no sequence point between the zero initialization and the assignment expression? Are sequence points even relevant (see above)? Or does zero initialization happen after all initializers are evaluated? If so, a[2] should end up being 0.

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

    Because the C standard does not clearly define what happens here, I believe the behavior is undefined (by omission).

    这篇关于关于 C 中数组初始化的困惑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆