在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的指针中)。

返回这样一个双标量字段的structmalloc 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"); } 

安全取决于结构本身是如何实现的。 我在实现类似的东西的时候偶然发现了这个问题,这是潜在的问题。

编译器在返回值时会执行一些操作(可能还有其他操作):

  1. 调用复制构造函数mystruct(const mystruct&)this是一个临时variables,由编译器本身分配的函数func 之外
  2. 调用func中分配的variables的析构函数~mystruct
  3. 调用mystruct::operator=如果返回的值被赋值给其他的=
  4. 调用编译器使用的临时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立在W​​indows上。

如果是这样,那么最新版本的桌面编译器的行为是什么:gcc,g ++和Visual Studio

你可以在Wine的邮件列表上find关于MS如何做的一些消息。