Dav*_*ave 23 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);
}
Run Code Online (Sandbox Code Playgroud)
将最后一行更改为
Foo f = foo(); printf("%s\n", f.f);
Run Code Online (Sandbox Code Playgroud)
工作良好.这两个版本在编译时都有效-std=c99
.我只是调用未定义的行为,或者标准中的某些内容已更改,这允许代码在C99下工作?为什么在C89下崩溃?
Kei*_*son 16
我相信在C89/C90和C99中都没有定义这种行为.
foo().f
是一个数组类型的表达式,具体而言char[25]
. C99 6.3.2.1p3说:
除了当它是的操作数的sizeof操作者或一元 和操作者,或是用于初始化一个数组,其具有输入"的阵列的表达一个字符串文字类型 "被转换为与类型"指针到表达型 "即指向数组对象的初始元素,而不是左值.如果数组对象具有寄存器存储类,则行为未定义.
在这种特殊情况下(一个函数返回的结构元素的数组)的问题是没有"数组对象".函数结果按值返回,因此调用的结果foo()
是type 的值struct Foo
,并且foo().f
是类型的值(不是左值)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);
}
Run Code Online (Sandbox Code Playgroud)
Aar*_*aid 13
printf
有点好笑,因为它是那些需要varargs的功能之一.所以让我们通过编写辅助函数来分解它bar
.我们稍后再回来printf
.
(我正在使用"gcc(Ubuntu 4.4.3-4ubuntu5)4.4.3")
void bar(const char *t) {
printf("bar: %s\n", t);
}
Run Code Online (Sandbox Code Playgroud)
然后调用它:
bar(foo().f); // error: invalid use of non-lvalue array
Run Code Online (Sandbox Code Playgroud)
好的,这给出了一个错误.在C和C++中,不允许按值传递数组.例如,您可以通过将数组放在结构中来解决此限制void bar2(Foo f) {...}
但我们没有使用该解决方法 - 我们不允许按值传递数组.现在,您可能认为它应该衰减为a char*
,允许您通过引用传递数组.但是,只有当数组具有地址(即是左值)时,衰减才有效.但临时性,例如来自功能的返回值,生活在一个他们没有地址的魔法之地.因此你不能拿&
一个临时的地址.简而言之,我们不允许采用临时的地址,因此它不能衰减到指针.我们无法通过值(因为它是一个数组)传递它,也不能通过引用(因为它是临时的)传递它.
我发现以下代码有效:
bar(&(foo().f[0]));
Run Code Online (Sandbox Code Playgroud)
但说实话,我认为这是可疑的.这不违反我刚刚列出的规则吗?
只是为了完成,这应该完美地工作:
Foo f = foo();
bar(f.f);
Run Code Online (Sandbox Code Playgroud)
变量f
不是临时的,因此我们可以(隐含地,在衰变期间)获取其地址.
我答应再提printf
一次.根据以上所述,它应该拒绝将foo().f传递给任何函数(包括printf).但是printf很有趣,因为它是vararg函数之一.gcc允许自己按值将数组传递给printf.
当我第一次编译并运行代码时,它处于64位模式.在我编译32位(-m32
到gcc)之前,我没有看到我的理论的确认.果然,我得到了一个段错误,就像最初的问题一样.(我得到了一些乱码输出,但是在64位时没有段错误).
我实现了自己的my_printf
(用vararg废话)打印了实际值,char *
然后尝试打印出来的字母char*
.我这样打电话:
my_printf("%s\n", f.f);
my_printf("%s\n", foo().f);
Run Code Online (Sandbox Code Playgroud)
这是我得到的输出(ideone上的代码):
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
Run Code Online (Sandbox Code Playgroud)
第一个指针值0xffc14eb3
是正确的(它指向字符"Hello,world!"),但请看第二个0x6c6c6548
.这是ASCII代码Hell
(反向顺序 - 小字节序或类似的东西).它已按值将数组复制到printf中,前四个字节已被解释为32位指针或整数.此指针不指向任何合理的位置,因此程序在尝试访问该位置时崩溃.
我认为这违反了标准,仅仅是因为我们不应该允许按值复制数组.
归档时间: |
|
查看次数: |
2732 次 |
最近记录: |