在C或C ++中返回结构是否安全?
我所理解的是这不应该做,但我相信我已经看到了一些例子,做这样的事情(注意代码不一定在语法上是正确的,但想法是在那里)
typedef struct{ int a,b; }mystruct;
然后这是一个function
mystruct func(int c, int d){ mystruct retval; retval.a = c; retval.b = d; return retval; }
我明白,如果我们想要做这样的事情,我们应该总是返回一个指向malloc'ed结构体的指针,但是我很积极,我见过类似这样的例子。 它是否正确? 就个人而言,我总是返回一个指向malloc'ed结构的指针,或者只是通过引用来传递函数并在那里修改值。 (因为我的理解是,一旦函数的作用域结束,不pipe用什么栈来分配结构都可以被覆盖)。
让我们在问题中增加第二部分:这是否因编译器而异? 如果是这样,那么最新版本的桌面编译器的行为是什么:gcc,g ++和Visual Studio?
关于这个问题的想法?
这是完全安全的,这样做没有错。 另外:它不会因编译器而异。
通常,当(比如你的例子)你的结构不是太大时,我会认为这种方法比返回一个malloc
结构要好( malloc
是一个昂贵的操作)。
这是完全安全的。
你正在回报的价值。 什么会导致未定义的行为是如果你通过参考返回。
//safe mystruct func(int c, int d){ mystruct retval; retval.a = c; retval.b = d; return retval; } //undefined behavior mystruct& func(int c, int d){ mystruct retval; retval.a = c; retval.b = d; return retval; }
您的代码段的行为是完全有效的和定义的。 它不会因编译器而异。 没关系!
就个人而言,我总是返回一个指向malloc'ed结构的指针
你不应该。 应尽可能避免dynamic分配内存。
或者只是通过引用来传递函数并修改其中的值。
这个选项是完全有效的。 这是一个select的问题。 一般来说,如果要在修改原始结构的同时从函数中返回其他内容,则可以这样做。
因为我的理解是,一旦函数的作用域结束,不pipe用什么栈来分配结构都可以被覆盖
这是错误的。 我的意思是,这是正确的,但是你返回你在函数中创build的结构的副本。 理论上 。 实际上, RVO可能也可能会发生。 阅读返回值优化。 这意味着虽然retval
在函数结束时似乎超出了范围,但实际上可能是在调用上下文中构build的,以防止额外的复制。 这是编译器可以自由执行的优化。
函数中mystruct
对象的生命期确实在离开函数时结束。 但是,您正在通过返回语句中的值传递对象。 这意味着该对象被从函数复制到调用函数中。 原来的对象已经消失了,但是复制品仍然存在。
这是完全合法的,但是大的结构有两个需要考虑的因素:速度和堆栈大小。
不仅在C中返回struct
是安全的(或者C ++中的class
,其中struct
-s实际上是具有默认public:
成员的类),但是很多软件都是这样做的。
当然,在C ++中返回一个class
时候,这个语言指定了一些析构函数或者移动构造函数会被调用,但是在很多情况下,编译器可以优化它们。
另外,Linux x86-64 ABI指定返回一个具有两个标量(例如指针或long
)值的结构是通过寄存器( %rax
& %rdx
)完成的,所以非常快速和高效。 因此,对于这种特殊情况,返回这种双标量字段struct
要比做其他任何事情更快(例如,将它们存储到作为parameter passing的指针中)。
返回这样一个双标量字段的struct
比malloc
it更快,并返回一个指针。
结构types可以是函数返回值的types。 这是安全的,因为编译器将创build一个结构副本,并返回该副本,而不是函数中的本地结构。
typedef struct{ int a,b; }mystruct; mystruct func(int c, int d){ mystruct retval; cout << "func:" <<&retval<< endl; retval.a = c; retval.b = d; return retval; } int main() { cout << "main:" <<&(func(1,2))<< endl; system("pause"); }
安全取决于结构本身是如何实现的。 我在实现类似的东西的时候偶然发现了这个问题,这是潜在的问题。
编译器在返回值时会执行一些操作(可能还有其他操作):
- 调用复制构造函数
mystruct(const mystruct&)
(this
是一个临时variables,由编译器本身分配的函数func
之外 ) - 调用
func
中分配的variables的析构函数~mystruct
- 调用
mystruct::operator=
如果返回的值被赋值给其他的=
- 调用编译器使用的临时variables的析构函数
~mystruct
现在,如果mystruct
和这里描述的一样简单,一切都很好,但是如果它有指针(比如char*
)或者更复杂的内存pipe理,那么这一切都取决于mystruct::operator=
, mystruct(const mystruct&)
和~mystruct
被执行。 因此,在将复杂的数据结构作为值返回时,我build议小心谨慎。
像你所做的那样返回一个结构是完全安全的。
然而,基于这个陈述: 因为我的理解是,一旦函数的作用域结束了,无论用什么栈来分配结构都可以被覆盖,我只想象一下这个结构的任何成员被dynamic分配的场景malloc'ed或new'ed),在这种情况下,没有RVO,dynamic分配的成员将被销毁,返回的副本将有一个成员指向垃圾。
我也会同意sftrabbit,生命确实结束了,堆栈区域被清除了,但编译器足够聪明,以确保所有的数据应该以寄存器或其他方式检索。
下面给出一个简单的确认示例(取自Mingw编译器组件)
_func: push ebp mov ebp, esp sub esp, 16 mov eax, DWORD PTR [ebp+8] mov DWORD PTR [ebp-8], eax mov eax, DWORD PTR [ebp+12] mov DWORD PTR [ebp-4], eax mov eax, DWORD PTR [ebp-8] mov edx, DWORD PTR [ebp-4] leave ret
你可以看到b的值已经通过edx传送了。 而默认的eax包含a的值。
返回结构是不安全的。 我喜欢自己做,但如果稍后有人将复制构造函数添加到返回的结构,复制构造函数将被调用。 这可能是意想不到的,可能会破坏代码。 这个bug很难find。
我有更详尽的答案,但版主不喜欢它。 所以,根据你的要求,我的提示很短。
让我们在问题中增加第二部分:这是否因编译器而异?
确实如此,正如我发现我的痛苦: http : //sourceforge.net/p/mingw-w64/mailman/message/33176880/
我在Win32(MinGW)上使用gcc来调用返回结构的COM接口。 原来,MS对GNU的做法不同,所以我的(gcc)程序崩溃了一个捣毁的堆栈。
这可能是MS可能在这里有更高的地位 – 但我所关心的是MS和GNU之间的ABI兼容性,以build立在Windows上。
如果是这样,那么最新版本的桌面编译器的行为是什么:gcc,g ++和Visual Studio
你可以在Wine的邮件列表上find关于MS如何做的一些消息。