c99 中的静态结构初始化

static struct initialization in c99

GCCc99/gnu99 模式下使用复合文字进行静态结构初始化时,我遇到了一个奇怪的行为。

显然这没问题:

struct Test
{
    int a;
};

static struct Test tt = {1}; /* 1 */

然而,这不是:

static struct Test tt = (struct Test) {1}; /* 2 */

这会触发以下错误:

initializer element is not constant

这也无济于事:

static struct Test tt = (const struct Test) {1}; /* 3 */

我明白静态结构的初始化值应该是编译时常量。但我不明白为什么这个最简单的初始化表达式不再被视为常量?这是由标准定义的吗?

我问的原因是我遇到了一些在 gnu90 模式下用 GCC 编写的遗留代码,它们使用这种复合文字构造进行静态结构初始化 (2)。显然这是当时的 GNU 扩展,后来被 C99 采用。

现在导致用GNU90成功编译的代码既不能用C99编译,也不能用GNU99编译。

他们为什么要这样对我?

这个 is/was a gcc bugHT 到 cremno),错误报告说:

I believe we should just allow initializing objects with static storage duration with compound literals even in gnu99/gnu11. [...] (But warn with -pedantic.)

我们从gcc document on compound literals可以看出,静态存储持续时间的对象的初始化应该作为扩​​展支持:

As a GNU extension, GCC allows initialization of objects with static storage duration by compound literals (which is not possible in ISO C99, because the initializer is not a constant).

这已在 gcc 5.2 中修复。因此,在 gcc 5.2 中,您只会在使用 -pedantic 标志 see it live 时收到此警告,如果没有 -pedantic.

则不会抱怨

使用 -pedantic 意味着 gcc should 提供标准要求的诊断:

to obtain all the diagnostics required by the standard, you should also specify -pedantic (or -pedantic-errors if you want them to be errors rather than warnings)

复合文字不是 C99 草案标准部分6.6 常量表达式所涵盖的常量表达式,我们从 6.7.8 初始化部分看到:

All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals.

gcc 允许接受其他形式的常量表达式作为扩展,来自 6.6:

部分

An implementation may accept other forms of constant expressions.

有趣的是,clang 不会使用 -pedantic

抱怨这个

引用 C11 标准,第 §6.5.2.5 章,复合文字,第 3 段,(强调我的

A postfix expression that consists of a parenthesized type name followed by a brace-enclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.

因此,复合文字被视为未命名对象,不被视为编译时常量。

就像你不能使用另一个变量来初始化静态变量一样,从C99开始,你不能再使用这个复合文字来初始化静态变量了。

ISO C99 支持复合文字 (according to this)。但是,目前 GNU 扩展提供具有 静态存储持续时间 [=53= 的对象初始化] 通过复合文字,但仅适用于 C90 和 C++。

复合文字看起来像是包含初始值设定项的强制转换。它的值是一个在强制转换中指定类型的对象,包含初始化器中指定的元素;它是一个左值。作为扩展, GCC 在 C90 模式和 C++ 中支持复合文字,虽然语义在 C++ 中有些不同。

通常,指定的类型是一个结构体。假设 struct foo 和结构声明如下所示:

 struct foo {int a; char b[2];} structure;

下面是一个用复合字面量构造 struct foo 的例子:

 structure = ((struct foo) {x + y, 'a', 0});

相当于这样写:

 {
   struct foo temp = {x + y, 'a', 0};
   structure = temp;
 }

GCC 扩展:
作为 GNU 扩展,GCC 允许通过复合文字初始化具有静态存储持续时间的对象(这在 ISO C99 中是不可能的,因为初始化器不是常量)。如果复合文字的类型和对象匹配,则处理就好像对象仅用括号括起来的列表初始化一样。复合文字的初始化列表必须是常量。如果正在初始化的对象具有未知大小的数组类型,则大小由复合文字大小决定。

 static struct foo x = (struct foo) {1, 'a', 'b'};
 static int y[] = (int []) {1, 2, 3};
 static int z[] = (int [3]) {1};

注:
post 上的编译器标签仅包含 GCC;但是,您将与 C99(以及多个 GCC 版本)进行比较。重要的是要注意 GCC 比更大的 C 标准组更快地为其编译器添加扩展功能。这有时会导致错误行为和版本之间的不一致。同样重要的是要注意,对一个众所周知和流行的编译器的扩展,但不符合公认的 C 标准,会导致潜在的不可移植代码。在决定使用尚未被大型 C 工作 groups/standards 组织接受的扩展时,始终值得考虑目标客户。 (参见 ISO (Wikipedia) and ANSI (Wikipedia)。)

有几个例子,其中更小更灵活的开源 C 工作组或委员会通过添加扩展来响应用户群表达的兴趣。例如,the switch case range extension.

有趣的是,clang 不会抱怨这段代码,即使有 -pedantic-errors 标志。

这肯定是关于 C11 §6.7.9/p4 初始化(强调我的前进)

All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.

另一个要研究的子条款是 §6.5.2.5/p5 复合文字:

The value of the compound literal is that of an unnamed object initialized by the initializer list. If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.

和(为了完整性)§6.5.2.5/p4:

In either case, the result is an lvalue.

但这并不意味着这样的未命名对象可以被视为常量表达式。 §6.6 常量表达式除其他外说:

2) A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be.

3) Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.

10) An implementation may accept other forms of constant expressions.

虽然没有明确提及复合文字,因此我会解释这一点,它们在严格符合程序中作为常量表达式是无效的(因此我会说,clang 有错误)。

第 J.2 节 未定义的行为(资料性)还阐明:

A constant expression in an initializer is not, or does not evaluate to, one of the following: an arithmetic constant expression, a null pointer constant, an address constant, or an address constant for a complete object type plus or minus an integer constant expression (6.6).

同样,没有提及复合文字。

然而,隧道里有一盏灯。另一种完全净化的方法是传送 地址常量 这样的未命名对象。该标准在 §6.6/p9 中指出:

An address constant is a null pointer, a pointer to an lvalue designating an object of static storage duration, or a pointer to a function designator; it shall be created explicitly using the unary & operator or an integer constant cast to pointer type, or implicitly by the use of an expression of array or function type. The array-subscript [] and member-access . and -> operators, the address & and indirection * unary operators, and pointer casts may be used in the creation of an address constant, but the value of an object shall not be accessed by use of these operators.

因此你可以安全地用这种形式的常量表达式初始化它,因为这样的复合文字确实指定了对象的左值,它具有静态存储持续时间:

#include <stdio.h>

struct Test
{
    int a;
};

static struct Test *tt = &((struct Test) {1}); /* 2 */

int main(void)
{
    printf("%d\n", tt->a);

    return 0;
}

经检查,gcc 5.2.0 和 clang 3.6.

上的 -std=c99 -pedantic-errors 标志都能正常编译

请注意,与 C++ 相反,在 C 中,const 限定符对常量表达式没有影响。

C 语言依赖于常量表达式 的精确定义。仅仅因为某些东西看起来 "known at compile time" 并不意味着它满足 常量表达式 .

的正式定义

C 语言没有定义非标量类型的常量表达式。它允许实现引入自己的常量表达式类型,但标准定义的常量表达式仅限于标量类型。

也就是说,C语言没有为你的类型struct Test定义常量表达式的概念。 struct Test 的任何值都不是常数。您的复合文字 (struct Test) {1} 不是常量(也不是字符串文字),因此,它不能用作具有静态存储持续时间的对象的初始值设定项。向其添加 const 限定符不会改变任何内容,因为在 C 中 const 限定符与 常量表达式 [=] 的概念 无关 没有关系34=]。在这种情况下,它永远不会有任何区别。

请注意,您的第一个变体根本不涉及复合文字。它使用原始的 { ... } 初始化语法,里面有常量表达式。对于具有静态存储持续时间的对象,这是明确允许的。

所以,在最严格的意义上,用复合字面量初始化是非法的,而用普通 { ... } 初始化器初始化是可以的。一些编译器可能接受复合文字初始化作为扩展。 (通过扩展 constant expression 的概念或采用其他一些扩展路径。查阅编译器文档以弄清楚它编译的原因。)