一尘不染

返回包含数组的结构

linux

以下是gcc 4.4.4下的简单代码段错误

#include<stdio.h>

typedef struct Foo Foo;
struct Foo {
    char f[25];
};

Foo foo(){
    Foo f = {"Hello, World!"};
    return f;
}

int main(){
    printf("%s\n", foo().f);
}

将最后一行更改为

 Foo f = foo(); printf("%s\n", f.f);

工作良好。使用编译时,这两个版本均可使用-std=c99。我是在简单地调用未定义的行为,还是在标准中进行了某些更改,从而使代码可以在C99下工作?为什么在C89下崩溃?


阅读 197

收藏
2020-06-02

共1个答案

一尘不染

我相信C89 / C90和C99中的行为均未定义。

foo().f是数组类型的表达式,特别是char[25]C99 6.3.2.1p3说:

除非它是 sizeof 运算符或一元 运算符的操作数,或者是用于初始化数组的字符串文字,否则将类型为“ array of
type ”的表达式转换为类型为“ pointer to type
的表达式,指向数组对象的初始元素,并且不是左值。如果数组对象具有寄存器存储类,则该行为是不确定的。

在这种特殊情况下(作为函数返回的结构元素的数组)的问题是没有“数组对象”。函数结果按值返回,因此调用的结果foo()是type 的 struct Foo,并且foo().f是type 的值(不是左值)char[25]

据我所知,这是C语言(最多C99)中唯一可以使用数组类型的非左值表达式的情况。我想说的是,尝试访问它的行为并没有被遗漏所定义,这可能是因为该标准的作者(可以理解的恕我直言)没有想到这种情况。在不同的优化设置下,您可能会看到不同的行为。

新的2011 C标准通过发明新的存储类来修补这种情况。 N1570(链接到最新的C11草案)在6.2.4p8中说:

具有结构或联合类型的非左值表达式,其中结构或联合包含具有数组类型的成员(递归包括所有包含的结构和联合的成员)是指具有自动存储期限和 临时生存期
的对象。它的生命周期从对表达式进行求值开始,并且其初始值为表达式的值。当包含完整表达式或完整声明符的求值结束时,其生存期结束。任何试图使用临时生存期修改对象的尝试都会导致未定义的行为。

因此,程序的行为在C11中得到了很好的定义。但是,直到能够获得符合C11的编译器,最好的选择可能是将函数的结果存储在本地对象中(假设您的目标是工作代码而不是破坏编译器):

[...]
int main(void ) {
    struct Foo temp = foo();
    printf("%s\n", temp.f);
}
2020-06-02