【问题标题】:Can a designated initializer legally refer to the variable it's initializing in C99?指定的初始化程序可以合法地引用它在 C99 中初始化的变量吗?
【发布时间】:2021-01-02 17:51:56
【问题描述】:

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. 初始化应按初始化程序列表顺序进行,为特定子对象提供的每个初始化程序都将覆盖同一子对象的任何先前列出的初始化程序;所有未显式初始化的子对象都应隐式初始化,与具有静态存储持续时间的对象相同。
  1. 未指定初始化列表表达式中任何副作用发生的顺序

【问题讨论】:

  • 你为什么对C99特别感兴趣?
  • 请注意,将bar.a 初始化为 3 并不是.a = 3 的副作用,因为这不是赋值操作(有副作用);它是初始化语法的一部分。
  • @JensGustedt 我想选择在 Windows 上使用 MSVC 进行跨平台项目(尽管我看到了 C11 and C18 support was announced a couple days ago)。顺便说一句,我作为 C 初学者正在阅读您的书——读起来非常愉快。

标签: c initialization c99 c11 designated-initializer


【解决方案1】:

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

初始化列表表达式的计算相对于彼此的顺序是不确定的,因此任何副作用发生的顺序都是未指定的。

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

在这种情况下,在初始化表达式中使用同一聚合对象的(可能)未初始化元素必须导致不确定的值。

【讨论】:

    【解决方案2】:

    这个脚注

    1. 特别是,评估顺序不必与 子对象初始化顺序

    此报价

    23 初始化列表表达式的求值是 相对于彼此不确定地排序,因此 未指定任何副作用发生的顺序。152)

    表示这样的初始化

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

    调用未定义的行为,因为表达式 bar.a + 3 可以在数据成员 a 的初始化之前求值。

    特别是未定义的行为被定义为

    可能的未定义行为包括忽略情况 完全无法预料的结果

    【讨论】:

      【解决方案3】:

      鉴于类似:

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

      这样的定义不会有问题:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-12
        • 1970-01-01
        • 2016-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-11
        相关资源
        最近更新 更多