Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/linux/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 返回包含数组的结构_C_Linux_Gcc_Language Lawyer - Fatal编程技术网

C 返回包含数组的结构

C 返回包含数组的结构,c,linux,gcc,language-lawyer,C,Linux,Gcc,Language Lawyer,以下是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); } 很好。使用-std=c99编译时,这两个版本都可以工作。我只是调用未定义的行为,还是标准中的

以下是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);
}

很好。使用
-std=c99
编译时,这两个版本都可以工作。我只是调用未定义的行为,还是标准中的某些内容发生了更改,从而允许代码在C99下工作?为什么在C89下会崩溃?

printf
有点滑稽,因为它是需要执行的函数之一。因此,让我们通过编写一个helper函数
bar
来分解它。稍后我们将返回到
printf

(我使用的是“gcc(ubuntu4.4.3-4ubuntu5)4.4.3”)

取而代之的是:

bar(foo().f); // error: invalid use of non-lvalue array
好的,这是一个错误。在C和C++中,不允许按值传递数组。您可以通过将数组放在结构中来解决此限制,例如
voidbar2(foof){…}

但是我们没有使用这种变通方法——我们不允许按值传入数组。现在,您可能认为它应该衰减为
char*
,允许您通过引用传递数组。但是,只有当数组有地址(即是左值)时,衰减才起作用。但是临时变量,比如函数的返回值,生活在一个没有地址的魔域中。因此,您不能使用临时用户的地址
&
。简而言之,我们不允许获取临时地址,因此它不能衰减为指针。我们不能通过值传递它(因为它是一个数组),也不能通过引用传递它(因为它是临时的)

我发现以下代码有效:

bar(&(foo().f[0]));
但老实说,我认为这是可疑的。这不是违反了我刚才列出的规则吗

完整地说,这是一个完美的工程:

Foo f = foo();
bar(f.f);
变量
f
不是临时变量,因此我们可以(隐式地,在衰减期间)获取它的地址

printf、32位对64位和古怪 我答应再提一次printf。根据上面的说明,它应该拒绝将foo().f传递给任何函数(包括printf)。但是printf很有趣,因为它是vararg函数之一。gcc允许自己将数组按值传递给printf

当我第一次编译并运行代码时,它处于64位模式。直到我编译成32位(
-m32
到gcc),我才发现我的理论得到了证实。果然,我犯了一个错误,就像原来的问题一样。(我得到了一些乱七八糟的输出,但在64位时没有segfault)

我实现了我自己的
my_printf
(使用vararg无意义),它在打印
char*
指向的字母之前打印了
char*
的实际值。我这样称呼它:

my_printf("%s\n", f.f);
my_printf("%s\n", foo().f);
这是我得到的输出():

第一个指针值
0xffc14eb3
是正确的(它指向字符“Hello,world!”),但请看第二个
0x6c6548
。这是地狱的ASCII码(逆序-小尾端或类似的东西)。它将数组按值复制到printf中,前四个字节被解释为32位指针或整数。此指针不指向任何合理的位置,因此程序在尝试访问该位置时崩溃


我认为这违反了标准,只是因为我们不允许按值复制数组。

在MacOS X 10.7.2上,GCC/LLVM 4.2.1('i686-apple-darwin11-LLVM-GCC-4.2(GCC)4.2.1(基于apple Inc.build 5658)(LLVM build 2335.15.00')和GCC 4.6.1(我构建的)以32位和64位模式编译无警告的代码(在
-Wall-Wextra
下)。这些程序运行时都没有崩溃。这就是我所期望的;代码在我看来很好


也许Ubuntu上的问题是特定版本的GCC中的一个bug,这个bug已经被修复了?

我相信C89/C90和C99中都没有定义这个行为

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

除非它是sizeof运算符的操作数或一元数 &运算符,或用于初始化数组的字符串文字,或 类型为“类型的数组”的表达式转换为 类型为“指针指向类型”且指向初始值的表达式 数组对象的元素,并且不是左值。如果数组对象 已注册存储类,行为未定义

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

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

新的2011年C标准通过发明一种新的存储类来修补这个死角。(链接到C11之前的最新草案)如6.2.4p8所示:

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

因此,程序的行为在C11中有很好的定义。直到你得到C
Foo f = foo();
bar(f.f);
my_printf("%s\n", f.f);
my_printf("%s\n", foo().f);
arg = 0xffc14eb3        // my_printf("%s\n", f.f); // worked fine
string = Hello, World!
arg = 0x6c6c6548        // my_printf("%s\n", foo().f); // it's about to crash!
Segmentation fault
[...]
int main(void ) {
    struct Foo temp = foo();
    printf("%s\n", temp.f);
}