为什么这个表达式被当作第一个成员的值,而不是结构体的值?
以下程序会得到一个奇怪的错误,这个错误似乎在暗示复合字面量把其中的表达式当作初始化结构的第一个成员来进行,而不是初始化整个结构。标准的哪一部分规定了这种行为?
// load_con
typedef struct load_con load_con;
struct load_con{
void* X;
int Y;
};
// Start main
int main( int a_c, char** a_l ){
load_con* a = (load_con[]){{.X=0,.Y=0}};
// Make a copy of *a
a = &(load_con){*a};
// Return
return 0;
}
当我用clang编译时,我得到:
error: initializing 'void *' with an expression of incompatible type 'load_con' (aka 'struct load_con')
a = &(load_con){*a};
GCC给出类似的错误。
在标准中我们有6.7.10.14:
具有自动存储期限的结构体或联合对象的初始化程序应为下列之一
一个初始化列表,如下所述,或者一个具有兼容的结构体或联合类型的单个表达式。在后者的情况下,对象的初始值(包括未命名的成员)即为该表达式的值。
这似乎暗示了我期望的行为,而不是出错的行为。
解决方案
结论
标准的哪一部分规定了这种行为?
这是C 2024 6.7.11,特别是这句话:
否则,具有聚合类型或联合类型的对象的初始化程序应为一个用花括号括起的对元素或命名成员的初始化器列表。
这看起来是标准中的一个小小的缺陷;你不能用一个结构来初始化一个结构的复合字面量。不过,你可以用 a = (load_con []) {*a}; 来变通。
分析
C 2024 6.5.3.6讨论了复合字面量,并且指出“6.7.11中对初始化列表的所有语义规则同样适用于复合字面量。”
6.7.11一般允许我们用同类型的对象初始化对象。例如,我们可以这样做:
int i = 3;
struct foo s = AnotherStructFoo;
然而,在6.5.3.6的复合字面量语法中,初始化部分是 花括号初始化器,它是6.7.11的初始化语法中的一个标记。所以我们要处理的并不是 3 或 AnotherStructFoo,而是 {3} 或 {AnotherStructFoo}。
int i = {3}; 之所以可行,是因为6.7.11规则中的一个特殊情况(粗体部分已添加):
标量的初始化应为单个表达式,可选地用花括号括起来,也可以是空初始化。
结构体没有任何特殊情况。在讨论了标量之后,6.7.11接着给出聚合(包括结构体)和联合的规则(粗体部分已添加):
否则,具有聚合类型或联合类型的对象的初始化程序应为一个用花括号括起的对元素或命名成员的初始化器。
因此,你的 (load_con) {…} 中的初始化器必须是对结构成员的初始化,而不是对结构本身的初始化。
不过,仍然有一个变通办法。你可以使用与你用于第一个复合字面量相同的数组类型:
a = (load_con []) {*a};
这会给你你想要的指针指向的拷贝。在另一种情况下,如果你想要的是实际的结构而不是指向它的指针,那么 * (load_con []) {*a} 同样也可行。
注
问题中提到这是标准草案中的这段文字:
具有自动存储期限的结构体或联合对象的初始化程序应为下述之一:要么是如下所述的初始化列表,要么是具有兼容结构体或联合类型的单个表达式。
这段文字表示你应该要么拥有“如下所述的初始化列表”,要么拥有“具有兼容结构体或联合类型的单个表达式”。你的初始化 {*a} 不是后者,因为它不是一个单一表达式。(表达式不能以 { 开头。)而且它也不是前者,如上所述。