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

查看:22
本文介绍了关于 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
", 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]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).

    附件 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天全站免登陆