函数返回另一个函数的返回值

Function returning the return of another function

提问人:blizpasta 提问时间:10/3/2008 最后编辑:Coding Mashblizpasta 更新时间:9/8/2012 访问量:843

问:

如果我想调用而不是 ,是否返回 Foo() 返回的副本(额外开销),或者它返回放置在临时堆栈上的相同对象?Bar()Foo()Bar()Foo()

vector<int> Foo(){  
    vector<int> result;  
    result.push_back(1);  
    return result;  
}  
vector<int> Bar(){  
    return Foo();  
}
C++ 函数 向量 返回值

评论


答:

2赞 Jorge Ferreira 10/3/2008 #1

通常,它会返回返回的 .但是,这在很大程度上取决于编译器所做的优化。请参阅以下讨论。vector<int>

调试生成

vector<int> Foo(){  
004118D0  push        ebp  
004118D1  mov         ebp,esp 
004118D3  push        0FFFFFFFFh 
004118D5  push        offset __ehhandler$?Foo@@YA?AV?$vector@HV?$allocator@H@std@@@std@@XZ (419207h) 
004118DA  mov         eax,dword ptr fs:[00000000h] 
004118E0  push        eax  
004118E1  sub         esp,0F4h 
004118E7  push        ebx  
004118E8  push        esi  
004118E9  push        edi  
004118EA  lea         edi,[ebp-100h] 
004118F0  mov         ecx,3Dh 
004118F5  mov         eax,0CCCCCCCCh 
004118FA  rep stos    dword ptr es:[edi] 
004118FC  mov         eax,dword ptr [___security_cookie (41E098h)] 
00411901  xor         eax,ebp 
00411903  push        eax  
00411904  lea         eax,[ebp-0Ch] 
00411907  mov         dword ptr fs:[00000000h],eax 
0041190D  mov         dword ptr [ebp-0F0h],0 
    vector<int> result;  
00411917  lea         ecx,[ebp-24h] 
0041191A  call        std::vector<int,std::allocator<int> >::vector<int,std::allocator<int> > (411050h) 
0041191F  mov         dword ptr [ebp-4],1 
    result.push_back(1);  
00411926  mov         dword ptr [ebp-0FCh],1 
00411930  lea         eax,[ebp-0FCh] 
00411936  push        eax  
00411937  lea         ecx,[ebp-24h] 
0041193A  call        std::vector<int,std::allocator<int> >::push_back (41144Ch) 
    return result;  
0041193F  lea         eax,[ebp-24h] 
00411942  push        eax  
00411943  mov         ecx,dword ptr [ebp+8] 
00411946  call        std::vector<int,std::allocator<int> >::vector<int,std::allocator<int> > (41104Bh) 
0041194B  mov         ecx,dword ptr [ebp-0F0h] 
00411951  or          ecx,1 
00411954  mov         dword ptr [ebp-0F0h],ecx 
0041195A  mov         byte ptr [ebp-4],0 
0041195E  lea         ecx,[ebp-24h] 
00411961  call        std::vector<int,std::allocator<int> >::~vector<int,std::allocator<int> > (411415h) 
00411966  mov         eax,dword ptr [ebp+8] 
}  

在这里,我们可以看到,在堆栈上创建了一个新对象vector<int> result;[ebp-24h]

00411917  lea         ecx,[ebp-24h] 
0041191A  call        std::vector<int,std::allocator<int> >::vector<int,std::allocator<int> > (411050h)

当我们到达一个新副本时,在调用方分配的存储中创建一个新副本return result;[ebp+8]

00411943  mov         ecx,dword ptr [ebp+8] 
00411946  call        std::vector<int,std::allocator<int> >::vector<int,std::allocator<int> > (41104Bh) 

并且为位于vector<int> result[ebp-24h]

0041195E  lea         ecx,[ebp-24h] 
00411961  call        std::vector<int,std::allocator<int> >::~vector<int,std::allocator<int> > (411415h) 

发布版本

vector<int> Foo(){  
00401110  push        0FFFFFFFFh 
00401112  push        offset __ehhandler$?Foo@@YA?AV?$vector@HV?$allocator@H@std@@@std@@XZ (401F89h) 
00401117  mov         eax,dword ptr fs:[00000000h] 
0040111D  push        eax  
0040111E  sub         esp,14h 
00401121  push        esi  
00401122  mov         eax,dword ptr [___security_cookie (403018h)] 
00401127  xor         eax,esp 
00401129  push        eax  
0040112A  lea         eax,[esp+1Ch] 
0040112E  mov         dword ptr fs:[00000000h],eax 
00401134  mov         esi,dword ptr [esp+2Ch] 
00401138  xor         eax,eax 
0040113A  mov         dword ptr [esp+8],eax 
    vector<int> result;  
0040113E  mov         dword ptr [esi+4],eax 
00401141  mov         dword ptr [esi+8],eax 
00401144  mov         dword ptr [esi+0Ch],eax 
    result.push_back(1);  
    return result;  
00401147  push        eax  
00401148  mov         dword ptr [esp+28h],eax 
0040114C  mov         ecx,1 
00401151  push        esi  
00401152  lea         eax,[esp+14h] 
00401156  mov         dword ptr [esp+10h],ecx 
0040115A  mov         dword ptr [esp+14h],ecx 
0040115E  push        eax  
0040115F  lea         ecx,[esp+1Ch] 
00401163  push        ecx  
00401164  mov         eax,esi 
00401166  call        std::vector<int,std::allocator<int> >::insert (401200h) 
0040116B  mov         eax,esi 
}  
0040116D  mov         ecx,dword ptr [esp+1Ch] 
00401171  mov         dword ptr fs:[0],ecx 
00401178  pop         ecx  
00401179  pop         esi  
0040117A  add         esp,20h 
0040117D  ret

该行不调用向量分配器,因为它是在 中的调用站点完成的。优化不会复制 Foo 的结果。vector<int> resultBar

评论

0赞 PierreBdR 10/3/2008
我确实认为这种解释有点矫枉过正......并且高度依赖编译器。
10赞 PierreBdR 10/3/2008 #2

两者都可能发生。但是,大多数编译器不会在优化后立即执行复制。

您的代码指示应该有一个副本。但是,允许编译器删除任何不更改语义和程序的副本。

注意:这就是为什么你永远不应该有一个复制构造函数,它除了正确复制之外做任何事情,因为你永远无法确定复制是否真的会完成。

评论

0赞 Martin York 10/3/2008
不幸的是,这不是允许编译器删除副本的地方(尽管内联会删除它)。所以是的,向量从 Foo() 复制到 Bar(),然后从 Bar() 复制到调用者。为了使复制 std::vector<> 非常高效,已经做了很多工作。所以别担心。
0赞 Evan Teran 10/3/2008
我认为编译器可以使用返回值优化 (msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx) 安全地将其作为单个副本进行优化。基本上,如果变量将被构造并立即复制,它可以直接构造到要复制的位置。
0赞 PierreBdR 10/4/2008
这绝对是进行优化的地方,即使没有内联。
0赞 blizpasta 10/4/2008
如果这两个函数属于不同的类别,它是否也会得到优化?实际上,这是我想问的实际问题,但忽略了正确提及条件。
2赞 Konrad Rudolph 10/3/2008 #3

对于 NRVO 来说,这是一个微不足道的案例——名称返回值优化(在这种情况下用词不当,因为没有名称)。斯坦·利普曼(Stan Lippman)在一篇博客文章中对所涉及的机制进行了很好的解释。

评论

1赞 David Pierre 10/3/2008
当临时没有命名时,它不就叫 RVO 吗?
1赞 Konrad Rudolph 10/3/2008
@David:是的,有些人,我自己也这样称呼它。但是,技术文献中似乎没有提到“RVO”。“NRVO”被用作包含此内容的技术术语。