17

GCC在in 中使用复合文字进行静态结构初始化时,我遇到了一个奇怪的行为c99/gnu99模式

显然这很好:

struct Test
{
    int a;
};

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

然而,这不是:

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

这会触发以下错误:

初始化元素不是常量

这也无济于事:

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

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

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

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

他们为什么要这样对我?

4

5 回答 5

10

是/曾经是一个 gcc 错误HT 到 cremno),错误报告说:

我相信我们应该只允许使用复合文字初始化具有静态存储持续时间的对象,即使在 gnu99/gnu11 中也是如此。[...](但用 -pedantic 警告。)

我们可以从关于复合文字的 gcc 文档中看到,具有静态存储持续时间的对象的初始化应该被支持作为扩展:

作为 GNU 扩展,GCC 允许通过复合文字初始化具有静态存储持续时间的对象(这在 ISO C99 中是不可能的,因为初始化器不是常量)。

这是固定在gcc 5.2. 因此,只有在使用flag see it livegcc 5.2时才会收到此警告,没有.-pedantic-pedantic

使用-pedantic意味着gcc 应该按照标准要求提供诊断:

要获得标准所需的所有诊断,您还应该指定 -pedantic(或 -pedantic-errors,如果您希望它们是错误而不是警告)

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

具有静态存储持续时间的对象的初始化程序中的所有表达式都应为常量表达式或字符串文字。

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

一个实现可以接受其他形式的常量表达式。

有趣的是,clang 并没有抱怨这个使用-pedantic

于 2015-08-06T18:56:42.400 回答
5

C 语言依赖于对常量表达式的精确定义。仅仅因为某些东西看起来“在编译时已知”并不意味着它满足常量表达式的正式定义。

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

换句话说,C 语言没有 为您的类型定义常量表达式struct Test的概念。的任何值struct Test都不是常数。您的复合文字(struct Test) {1}不是常量(也不是字符串文字),因此,它不能用作具有静态存储持续时间的对象的初始化程序。向它添加const限定符不会改变任何内容,因为在 C 中,const限定符与常量表达式的概念没有任何关系。在这种情况下,它永远不会产生任何影响。

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

因此,从最严格的意义上说,使用复合文字进行初始化是非法的,而使用普通{ ... }初始化程序进行初始化是可以的。一些编译器可能接受复合文字初始化作为扩展。(通过扩展常量表达式的概念或采用其他一些扩展路径。查阅编译器文档以找出它编译的原因。)

于 2015-08-06T21:50:20.947 回答
4

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

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

具有静态或线程存储持续时间的对象的初始化程序中的所有表达式都应为常量表达式或字符串文字。

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

复合文字的值是 由初始化列表初始化的未命名对象的值。如果复合文字出现在函数体之外,则该对象具有静态存储持续时间;否则,它具有与封闭块关联的自动存储持续时间。

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

无论哪种情况,结果都是左值

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

2) 常量表达式可以在翻译期间而不是运行时求值,因此可以在常量可能存在的任何地方使用。

3) 常量表达式不应包含赋值、递增、递减、函数调用或逗号运算符,除非它们包含在未计算的子表达式中。

10) 一个实现可以接受其他形式的常量表达式。

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

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

初始化程序中的常量表达式不是或不计算为以下之一:算术常量表达式、空指针常量、地址常量或完整对象类型的地址常量加或减整数常量表达式(6.6)。

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

尽管如此,隧道里还是有一盏灯。另一种完全净化的方法是将此类未命名对象传递为地址常量。该标准在 §6.6/p9 中规定:

地址常量是一个空指针,一个指向指定静态存储对象的左值的指针,或者一个指向函数指示符的指针;它应使用一元运算& 符或转换为指针类型的整数常量显式创建,或通过使用数组或函数类型的表达式隐式创建。数组下标[]和成员访问.->操作符、地址& 和间接*一元操作符以及指针转换可用于创建地址常量,但不得使用这些操作符访问对象的值。

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

#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;
}

经检查,它在5.2.0 和3.6-std=c99 -pedantic-errors上都可以使用标志编译。gccclang

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

于 2015-08-06T19:08:26.713 回答
3

ISO C99确实支持复合文字(根据 this)。然而,目前只有GNU 扩展通过复合字面量提供具有静态存储持续时间的对象的初始化,但仅适用于 C90 和 C++。

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

通常,指定的类型是结构。假设struct foo和结构声明如下

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

struct foo下面是使用复合文字构造 a 的示例:

 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};

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

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

于 2015-08-06T19:03:44.970 回答
1

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

由带括号的类型名称后跟用大括号括起来的初始化器列表组成的后缀表达式是复合文字它提供了一个未命名的对象,其值由初始化列表给出。

因此,复合文字被视为一个未命名的对象,它不被视为编译时间常数。

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

于 2015-08-06T19:03:38.487 回答