这是 gcc 中的一个错误。
在大多数情况下,数组类型的表达式会隐式转换为指向数组对象第一个元素的指针。例外情况是表达式是 (a) 一元运算符的操作数sizeof
;(b) 当它是一元运算符的操作数时&
;(c) 当它是用于初始化数组对象的初始化程序中的字符串文字时。这些例外都不适用于这里。
那个描述有各种各样的漏洞。它假定,对于任何给定的数组类型表达式,都有一个它引用的数组对象(即,所有数组表达式都是左值)。这几乎是真的,但是您遇到了一个极端情况。函数可以返回结构类型的结果。该结果只是 struct 类型的值,而不是引用任何对象。(这同样适用于工会,但我会忽略这一点。)
这:
struct foo { int n; };
struct foo func(void) {
struct foo result = { 42 };
return result;
}
原则上与此没有什么不同:
int func(void) {
int result = 42;
return result;
}
在这两种情况下,都会返回值的副本;result
该值可以在对象result
不复存在后使用。
但是如果返回的结构有一个数组类型的成员,那么你有一个数组是一个非左值结构的成员——这意味着你可以有一个非左值数组表达式。
在 C90 和 C99 中,尝试引用这样的数组(除非它是 的操作数sizeof
)具有未定义的行为——不是因为标准这么说,而是因为它没有定义行为。
struct weird {
int arr[10];
};
struct weird func(void) {
struct weird result = { 0 };
return result;
}
调用func()
给你一个类型的表达式struct weird
;这没有什么问题,例如,您可以将其分配给 type 的对象struct weird
。但是如果你写这样的东西:
(void)func().arr;
然后标准说数组表达式func().arr
被转换为指向它所引用的不存在对象的第一个元素的指针。这不仅仅是遗漏未定义行为的情况(标准明确指出仍然是未定义行为)。这是标准中的一个错误。在任何情况下,标准都无法定义行为。
在 2011 ISO C 标准 (C11) 中,委员会终于承认了这种极端情况,并创造了临时寿命的概念。 N1570 6.2.4p8 说:
具有结构或联合类型的非左值表达式,其中结构或联合包含具有数组类型的成员(递归地,包括所有包含的结构和联合的成员)指的是具有自动存储持续时间和临时生命周期的对象
它的生命周期开始于对表达式求值,其初始值为表达式的值。当包含完整表达式或完整声明符的评估结束时,它的生命周期结束。任何修改具有临时生命周期的对象的尝试都会导致未定义的行为。
带脚注:
当访问数组成员时,会隐式获取此类对象的地址。
因此,C11 解决这个难题的方法是创建一个临时对象,以便数组到指针的转换实际上会产生有意义的地址(具有临时生命周期的对象成员的元素)。
显然 gcc 中处理这种情况的代码不太正确。在 C90 模式下,它必须做一些事情来解决该版本标准中的不一致问题。显然,它被视为func().arr
非左值数组表达式(根据 C90 规则,这可能是正确的)——但随后它错误地允许将该数组值分配给数组对象。尝试分配给数组对象,无论分配右侧的表达式恰好是什么,显然违反了 C90 6.3.16.1 中的约束部分,如果 LHS 不是算术左值、指针、结构或联合类型。目前尚不清楚(根据 C90 和 C99 规则)编译器是否必须诊断如下表达式func().arr
,但它显然必须诊断出试图将该表达式分配给 C90、C99 或 C11 中的数组对象的尝试。
为什么这个错误出现在 C90 模式下而在 C99 模式下被正确诊断,这仍然有点神秘,因为据我所知,在 C90 和 C99 之间标准的这个特定区域没有显着变化(仅引入了临时生命周期在 C11 中)。但由于这是一个错误,我认为我们不能过多抱怨它出现的不一致。
解决方法:不要那样做。