以下代码编译并输出“字符串”作为输出。
#include <stdio.h>
struct S { int x; char c[7]; };
struct S bar() {
struct S s = {42, "string"};
return s;
}
int main()
{
printf("%s", bar().c);
}
显然,这似乎根据
我不明白关于“下一个序列点”的说法。这里发生了什么?
最佳答案
您已经遇到了该语言的一个微妙问题。
在大多数情况下,数组类型的表达式都隐式转换为指向数组对象第一个元素的指针。异常(exception)情况(在这里均不适用)是:
&
运算符的操作数(产生整个数组的地址)时; sizeof
或(从C11开始)的_Alignof
运算符的操作数时(sizeof arr
产生数组的大小,而不是指针的大小);和char str[6] = "hello";
不会将"hello"
转换为char*
。)(N1570草案错误地将
_Alignof
添加到异常(exception)列表中。实际上,由于不清楚的原因,_Alignof
只能应用于类型名称,而不能应用于表达式。)请注意,有一个隐式假设:数组表达式首先引用数组对象。在大多数情况下,它会这样做(最简单的情况是当数组表达式为已声明的数组对象的名称时),但是在这种情况下,没有数组对象。
如果函数返回结构,则结构结果按值返回。在这种情况下,该结构包含一个数组,至少在逻辑上为我们提供了一个没有对应数组对象的数组值。因此,数组表达式
bar().c
衰减为指向... er,um,...的第一个元素的指针,该元素不存在。2011 ISO C标准通过引入“临时生存期”解决了这一问题,“临时生存期”仅适用于“具有结构或 union 类型的非左值表达式,其中结构或 union
包含一个具有数组类型的成员”(N1570 6.2.4p8)。此类对象不可修改,并且其生存期在包含完整表达式或完整声明符的末尾结束。
因此,从C2011开始,您的程序的行为已得到很好的定义。
printf
调用获得一个指向数组第一个元素的指针,该数组是具有临时生存期的struct对象的一部分;该对象将继续存在,直到printf
调用完成为止。但是从C99开始,行为是不确定的-不一定是因为您引用的子句(据我所知,没有中间的序列点),而是因为C99没有定义对于
printf
起作用。如果您的目标是使该程序正常运行,而不是理解为什么可能会失败,则可以将函数调用的结果存储在显式对象中:
const struct s result = bar();
printf("%s", result.c);
现在,您有了一个具有自动而不是临时存储期限的struct对象,因此它在
printf
调用执行期间和之后都存在。