静态数组的条件初始化,使用复合字面量。

3
#include <stdint.h>

#define INIT_UINT32 1
#define INIT_INT32  2

#define INIT INIT_INT32

typedef union
{
  uint32_t a;
  int32_t  b;
} Foo_t;

/* Why does this compile... */
static Foo_t foo_static = INIT == INIT_INT32 ?
    (Foo_t){ .a = UINT32_MAX} : (Foo_t){ .b = INT32_MAX };

/* but this doesn't? */
static Foo_t foo_static_array[] =
{
  INIT == INIT_INT32 ? (Foo_t){ .a = UINT32_MAX} : (Foo_t){ .b = INT32_MAX }
};

int main(void)
{
}

编译时,使用复合字面量对foo_static进行条件初始化成功,但使用复合字面量对foo_static_array进行条件初始化失败。以下是编译错误。
$ gcc test.c
test.c:4:21: error: initializer element is not constant
    4 | #define INIT_INT32  2
      |                     ^
test.c:6:14: note: in expansion of macro ‘INIT_INT32’
    6 | #define INIT INIT_INT32
      |              ^~~~~~~~~~
test.c:21:3: note: in expansion of macro ‘INIT’
   21 |   INIT == INIT_INT32 ? (Foo_t){ .a = UINT32_MAX} : (Foo_t){ .b = INT32_MAX }
      |   ^~~~
test.c:4:21: note: (near initialization for ‘foo_static_array[0]’)
    4 | #define INIT_INT32  2
      |                     ^
test.c:6:14: note: in expansion of macro ‘INIT_INT32’
    6 | #define INIT INIT_INT32
      |              ^~~~~~~~~~
test.c:21:3: note: in expansion of macro ‘INIT’
   21 |   INIT == INIT_INT32 ? (Foo_t){ .a = UINT32_MAX} : (Foo_t){ .b = INT32_MAX }
      |   ^~~~

有人能解释一下为什么会这样吗?


@Someprogrammerdude:两个初始化程序都受C 2018 6.7.9 4的管理:“静态或线程存储期对象的初始化程序中的所有表达式都应为常量表达式或字符串字面值。” C标准中没有规定非数组的初始化程序是在运行时完成的。 - Eric Postpischil
我猜是“编译时常量”的概念让我感到困惑。表达式的值,“INIT == INIT_INT32 ? (Foo_t){ .a = UINT32_MAX} : (Foo_t){ .b = INT32_MAX }”可以在编译时计算,那为什么它不被认为是“编译时常量”呢? - somethingsfishy
某些东西可以在编译时计算并不意味着C标准要求编译器在编译时计算它,或者编译器确实在编译时计算它。 C标准在第6.6条款中指定了“常量表达式”。 - Eric Postpischil
旁注:这些条件表达式中的逻辑似乎被颠倒了。当 INIT == INIT_INT32 为真时,我本来期望选择带有 .b = INT32_MAX 的初始化器。 - Ian Abbott
1个回答

5
GCC提供了前者初始化作为C标准的扩展,但不提供后者。这是GCC的选择,而不是标准规定。使用-pedantic,GCC会抱怨两者。
C标准中相关的段落是C 2018 6.7.9 4:
所有具有静态或线程存储期的对象的初始化程序中的所有表达式都应该是常量表达式或字符串字面值。
这在约束部分中,这意味着符合标准的编译器必须对其进行诊断,就像使用-pedantic时GCC所做的那样,尽管它仍然接受代码并完成编译。没有-pedantic,GCC接受两者,但只诊断后者。
GCC关于C扩展的文档对于这种差异的原因保持沉默。它的第6.27条说,具有自动存储期的聚合体的初始化程序不需要是常量表达式,但C扩展子句中没有一个子句涉及静态对象的初始化程序。
为使您的代码严格符合规范,您不应在非数组或数组初始化中使用这些初始化程序。

@NateEldredge 因为复合字面量不是常量表达式,尽管在具有静态存储期的复合字面量的地址是一个常量表达式。 - Ian Abbott
但是即使使用-pedanticFoo_t static_foo = (Foo_t){.a = 0};也不会被诊断出来:https://godbolt.org/z/76M7rY7eE。你是说这是6.6p10下的“其他形式”吗?我记得在标准的其他地方看到过允许这样做的建议;我会试着去查找一下。 - Nate Eldredge
为什么GCC在某些初始化程序中将条件表达式中的复合字面量接受为常量表达式,但不接受静态数组? - Ian Abbott
“常量表达式”是一个定义的术语;请参阅C17 6.6。语言规范没有定义或要求实现识别具有联合或聚合类型的任何常量表达式(“文字”!=“常量”)。它明确允许实现接受除规范中描述的常量表达式形式之外的形式,但这并不使得这种识别比它本来就是的扩展更少。我同意GCC在这个领域似乎有点不一致,但这是依赖语言扩展时所面临的风险之一。 - John Bollinger
@JohnBollinger:如果一个实现定义了一个复合文字,具有常量表达式初始化器,使其成为常量表达式,如6.6 10所允许的那样,则将此类复合文字用作静态对象的初始化器不会违反6.7.9 4中对静态对象初始化器必须是常量表达式的约束条件,因此不需要诊断。如果一个实现没有将这样的复合文字定义为常量表达式,但接受它作为非常量表达式静态对象初始化器,则该初始化器违反6.7.9 4 并且必须进行诊断。 - Eric Postpischil
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接