以下代码编译并输出“字符串”作为输出。

#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调用执行期间和之后都存在。

    10-07 19:11
    查看更多