请思考:
typedef struct { int a[1]; } S;
const S foo(void) { return (S) {{3}}; }
void bar(void) { int *p = foo().a; }
根据Per C 2018 6.2.4 8,表达式
foo()
引用一个有临时存活期的对象:
结构体或联合体类型的非左值表达式(其中,结构体或联合体包含一个具有数组类型的成员(包括所有包含的结构体和联合体的成员)),指向具有自动存储期和临时生存期的对象。它的生存期始于评估该表达式时,并且其初始值是表达式的值。当包含完整表达式的评估结束时,其生存期结束。任何试图修改具有临时生存期的对象将导致未定义行为。对于有效类型的目的,具有临时生存期的对象的行为好像是使用其值的类型声明的。这样的对象不需要唯一的地址。
注意,代码没有尝试修改对象,但它确实使用foo().a
的第一个元素的地址初始化了int *
,我们希望这个地址应该是const
限定的。这违反了6.5.16.1 1中所需求的指向目标类型必须具有指向源类型的所有限定符的约束条件。(该段落适用于赋值,但已由6.7.9 11纳入其中,该标准涵盖了初始化。)
但是,无论是GCC还是Clang都没有对这种初始化进行投诉。实际上,使用-Wextra
时,Clang会抱怨foo
的返回类型,称“返回类型上的'const'限定符没有效果”。
GCC和Clang将foo()
视为未被const
限定,是错误的吗?还是C标准中有关于返回类型上的限定符被忽略的规定?
(如果有人抱怨没有用处,请考虑baz(foo().a)
,其中数组被传递给一个在其为临时对象时大量使用它的函数。然而,如果baz
修改了数组的任何部分,则该行为将不被C标准定义。程序员可能希望通过声明foo
的返回类型为const
来防止此错误,因此期望在不带相应const
的函数参数声明中意外传递临时对象的地址时收到编译器错误消息。)
T
**”。因此,foo
的类型为S(void)
。请参考https://www.iso-9899.info/n3047.html#6.7.6.3p4和c17_updated_proposed_fdis.pdf。 - Language Lawyerint *p = foo().a; baz(); printf("%d\n",*p);
中,调用baz
是否破坏了从foo
返回的值?通常的做法是:S copy = foo();
然后,弹出栈以回收临时区域。但是,仅仅取S.a
的地址就足以保留隐式的栈上副本吗? - Craig Estey