在GCC
模式下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
进行编译。
他们为什么要这样对我?
答案 0 :(得分:10)
这个is/was a gcc bug( HT to cremno ),错误报告说:
我相信我们应该只允许使用static初始化对象 使用复合文字的存储持续时间,即使在gnu99 / gnu11中也是如此。 [...] (但是警惕地说。)
我们可以从gcc document on compound literals看到,应该支持具有静态存储持续时间的对象的初始化作为扩展:
作为GNU扩展,GCC允许使用static初始化对象 复合文字的存储持续时间(这在ISO中是不可能的) C99,因为初始化程序不是常量)。
这已在gcc 5.2
中修复。因此,在gcc 5.2
中,您只会在使用-pedantic
标记see it live时收到此警告,-pedantic
不会在没有-pedantic
的情况下抱怨。
使用6.6
表示gcc should按标准要求提供诊断:
要获得标准所需的所有诊断,您应该 如果你想要它们,也可以指定-pedantic(或-pedantic-errors) 错误而不是警告)
复合文字不是C99标准草案部分所涵盖的常量表达式6.7.8
常量表达式,我们从章节6.6
初始化中看到:
具有静态存储持续时间的对象的初始值设定项中的所有表达式都应为 常量表达式或字符串文字。
允许gcc接受其他形式的常量表达式作为扩展,来自-pedantic
部分:
实现可以接受其他形式的常量表达式。
有趣的是,clang并没有使用plot.data
答案 1 :(得分: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节未定义的行为(提供信息)也澄清了:
初始值设定项中的常量表达式未评估或未评估 to,以下之一:算术常量表达式,null 指针常量,地址常量或地址常量 完整对象类型加上或减去整数常量表达式 (6.6)。
同样,没有提及复合文字。
不过,隧道里有一盏灯。另一种完全消毒的方法是将这种未命名的对象传达为地址常量。标准在§6.6/ p9中指出:地址常量是一个空指针,指向左值的指针 指定静态存储持续时间的对象,或指向a的指针 功能指示符;它应该使用一元
&
显式创建 运算符或整数常量强制转换为指针类型,或隐式地通过 使用数组或函数类型的表达式。该 数组下标[]
和成员访问.
和->
运算符,地址&
和间接*
一元运算符和指针强制转换可以用于 创建一个地址常量,但对象的值应该是 不能通过使用这些操作符来访问。
因此你可以安全地使用这种形式的常量表达式来初始化它,因为这样的复合文字确实指定了一个对象的左值,它具有静态存储持续时间:
#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;
}
如果选中,它会-std=c99 -pedantic-errors
5.2.0和gcc
3.6上的clang
标记编译得很好。
注意,与C ++相反,在C中,const
限定符对常量表达式没有影响。
答案 2 :(得分:4)
C语言依赖于常量表达式的确切定义。只是因为在编译时看起来有些东西&#34;并不意味着它满足常量表达式的正式定义。
C语言没有定义非标量类型的常量表达式。它允许实现引入自己的常量表达式,但标准定义的常量表达式仅限于标量类型。
换句话说,C语言没有为您的类型struct Test
定义常量表达式的概念。 struct Test
的任何值都不是常量。您的复合文字(struct Test) {1}
不是常量(并且不是字符串文字),因此,它不能用作具有静态存储持续时间的对象的初始值设定项。添加const
限定符不会改变任何内容,因为在C const
限定符与常量表达式的概念之间没有无论。在这种情况下它永远不会有任何区别。
请注意,您的第一个变体根本不涉及复合文字。它使用带有常量表达式的原始{ ... }
初始化语法。具有静态存储持续时间的对象明确允许这样做。
因此,在最严格的意义上,使用复合文字进行初始化是非法的,而使用普通{ ... }
初始化程序初始化则很好。某些编译器可能会接受复合文字初始化作为扩展。 (通过扩展常量表达式的概念或采用其他一些扩展路径。请参阅编译器文档以找出它编译的原因。)
答案 3 :(得分:3)
ISO C99 支持复合文字( according to this )。但是,目前只有 GNU扩展程序通过复合文字提供 静态存储持续时间 的对象初始化,但仅限于对于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};
<强> 注意:的强>
帖子上的编译器标签仅包含GCC;但是,您可以与C99(以及多个GCC版本)进行比较。值得注意的是,GCC比其他更大的C标准组更快地为其编译器添加扩展功能。这有时会导致错误的行为和版本之间的不一致。同样重要的是,对于众所周知且受欢迎的编译器的扩展,但不符合公认的C标准,会导致潜在的非可移植代码。在决定使用尚未被较大的C工作组/标准组织接受的扩展时,始终值得考虑目标客户。 (请参阅ISO (Wikipedia)和ANSI (Wikipedia)。)
有几个例子,较小的更灵活的开源C工作组或委员会通过添加扩展来响应用户群表示的兴趣。例如,the switch case range extension。
答案 4 :(得分:1)
引用C11
标准,章节§6.5.2.5,复合文字,第3段,(强调我的)
后缀表达式由带括号的类型名称后跟括号括起的初始值设定项列表组成,是一个复合文字。 它提供了一个未命名的对象,其值由初始化列表提供。
因此,复合文字作为一个未命名的对象,它不被视为编译时常量。
就像你不能使用另一个变量来初始化一个静态变量,前面的C99,你不能再使用这个复合文字来初始化一个静态变量。