指定的初始值设定项可以合法地引用它在 C99 中初始化的变量吗?

Can a designated initializer legally refer to the variable it's initializing in C99?

GCC 和 Clang 都允许指定的初始化程序引用正在初始化的结构或数组的成员,但这种行为是否合法且定义明确?

以下代码示例为 GCC 和 Clang 编译和运行,并在两种情况下输出 { .a = 3, .b = 6, }

#include <stdio.h>

typedef struct
{
    int a;
    int b;
} foo;

int main()
{
    foo bar = {
        .a = 3,
        .b = bar.a + 3,
    };
    printf("{ .a = %d, .b = %d, }\n", bar.a, bar.b);

    return 0;
}

GCC 为指定的初始化生成以下输出 (Compiler Explorer link),这表明此示例的操作是安全的:

mov     dword ptr [rbp - 4], 0
mov     dword ptr [rbp - 16], 3
mov     eax, dword ptr [rbp - 16]
add     eax, 3
mov     dword ptr [rbp - 12], eax

draft C99 spec 的第 6.7.8 节对此进行了讨论,但我不明白它如何以某种方式定义此行为。

特别是,第 19 点建议初始化按指定顺序进行,但第 23 点提到副作用具有未指定的顺序。我不确定写入结构的数据是否被视为副作用。

  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.
  1. The order in which any side effects occur among the initialization list expressions is unspecified

您引用的是旧版本的 C 标准。当前草案(自 C11 起)对于第 23 点有:

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.

我认为这意味着编译器可以选择在使用该表达式之前的任何时间评估特定的初始化表达式,这意味着它可能发生在它引用的元素之前或之后已初始化。

既然如此,在初始化表达式中使用同一聚合对象的(可能)未初始化元素必须导致不确定值。

这个foot-note

  1. In particular, the evaluation order need not be the same as the order of subobject initialization

此报价

23 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.152)

表示这样的初始化

foo bar = {
        .a = 3,
        .b = bar.a + 3,
    };

调用未定义的行为,因为表达式 bar.a + 3 可以在数据成员 a.

初始化之前计算

未定义的行为特别定义为

Possible undefined behavior ranges from ignoring the situation completely with unpredictable results

给出类似的东西:

struct foo {int arr[10], *p;};

像这样的定义不会有问题:

struct foo x = {.p = x.arr, .arr={1,2,3,4,5}};

引用正在构造的对象,因为在构造完成之前,这样的引用不会用于实际访问对象。可能存在一些极端情况,其中尝试访问对象会产生定义的结果(例如,如果一个成员的初始化表达式作为副作用将它已经初始化或将要初始化的值存储到另一个成员),但我不要认为该标准的作者在任何实质性细节上考虑此类情况并确定是否应对其进行定义。我认为很明显,确定和写入结构成员的初始值所涉及的操作相对于彼此的顺序是不确定的,这意味着如果结构成员被初始化本身以外的其他操作访问,则此类操作可能发生在或之前在该成员被初始化之后,这种排序可能会产生任何后果。