首先是什么 - 堆栈展开或复制返回值

时间:2012-01-19 08:27:56

标签: c++

方法GetValues()中使用的互斥锁是否在之前发布之后复制构建dummy实例?

class Protect
{};

class Test
{
public:
    Protect GetValues() const;

private:
    Protect m_protVal;
    Mutex m_mutex;
};

Protect Test::GetValues() const
{
    CLockGuard lock(m_mutex);

    return m_protVal;
}

int main(int argc, char** argv)
{
    Test myTestInstance;

    Protect dummy = myTestInstance.GetValues();
}

我们假设CLockGuardMutex是boost lib提供的标准类。

4 个答案:

答案 0 :(得分:12)

是:-)。正式地,返回时有两个“副本” value:用于实际返回值的一个特殊位置,和 返回后的第二个,到最后值必须到达的地方 放置。然而,可以优化其中之一或两者。破坏 局部变量发生在第一个之后,但在第二个之前。 (NRVO 和RVO可能导致第一个被优化,但它们不会影响 你的代码,因为你没有返回一个局部变量。)

答案 1 :(得分:2)

从Visual C ++开始,使用MSVC 9.0,以下代码

int add(int x, int y)
{
    int z;
    z = x + y;
    return z;
}

int _tmain(int argc, _TCHAR* argv[])
{
    add(10, 20);
    return 0;
}

导致汇编

int add(int x, int y)
{
013613A0  push        ebp  //save the frame pointer
013613A1  mov         ebp,esp 
013613A3  sub         esp,0CCh 
013613A9  push        ebx  
013613AA  push        esi  
013613AB  push        edi  
013613AC  lea         edi,[ebp-0CCh] 
013613B2  mov         ecx,33h 
013613B7  mov         eax,0CCCCCCCCh 
013613BC  rep stos    dword ptr es:[edi] 
    int z;
    z = x + y;
013613BE  mov         eax,dword ptr [x] //load x
013613C1  add         eax,dword ptr [y] //add y to x
013613C4  mov         dword ptr [z],eax //store the result to z
    return z;
013613C7  mov         eax,dword ptr [z] //store the return value in eax
}
013613CA  pop         edi  //unwind the stack
013613CB  pop         esi  
013613CC  pop         ebx  
013613CD  mov         esp,ebp 
013613CF  pop         ebp  
013613D0  ret

int _tmain(int argc, _TCHAR* argv[])
{    
013613E0  push        ebp  
013613E1  mov         ebp,esp 
013613E3  sub         esp,0C0h 
013613E9  push        ebx  
013613EA  push        esi  
013613EB  push        edi  
013613EC  lea         edi,[ebp-0C0h] 
013613F2  mov         ecx,30h 
013613F7  mov         eax,0CCCCCCCCh 
013613FC  rep stos    dword ptr es:[edi] 
    add(10, 20);
013613FE  push        14h  
01361400  push        0Ah  
01361402  call        add (136109Bh) 
01361407  add         esp,8 
    return 0;
0136140A  xor         eax,eax //store 0 to eax, the return value holder
}
0136140C  pop         edi  //unwind the stack
0136140D  pop         esi  
0136140E  pop         ebx  

这让我说首先存储返回值,然后发生堆栈展开!

答案 2 :(得分:2)

就我所知,标准并不是特别明确,但这是我设法聚集的内容:

  

当阻止它们时,自动存储持续时间对象按6.7销毁   在出口宣布。    - 3.7.2

     

从范围退出时,将为所有自动存储调用析构函数(12.4)   在该范围内声明的持续时间(3.7.2)(命名对象和临时对象),   按其声明的相反顺序。    - 6.6

     

表达式为非void类型的return语句只能在函数中使用   返回一个值;表达式的值返回给调用者   功能。表达式隐式转换为函数的返回类型   它出现在哪里。退货声明可能涉及构建和复制   临时对象(12.2)。    - 6.6.3

     

即使避免了临时对象的创建(12.6),也都是语义   必须遵守限制,就像创建临时对象一样。    - 12.2

这似乎总体上证实了James所说的:在return m_protVal;上创建了一个临时文件,然后按照其声明的相反顺序调用所有必须销毁的对象的析构函数(在这种情况下,只有析构函数) lock被称为)。但是,我不完全确定如何解释以下内容:

  

临时对象作为评估完整表达式的最后一步被销毁(1.9)   (词汇​​上)包含创建它们的点。    - 12.2

完整表达式被定义为“作为不是另一个表达式的子表达式的表达式”。我不知道return m_protVal的哪一部分是完整的表达式:geordi说这是一个

  

decl'ion-seq→decl'ion→func-def→func-body→compound-stmt→stmt-seq→stmt→jump-stmt

i本身就是

  

decl'ion-seq→decl'ion→func-def→func-body→compound-stmt→stmt-seq→stmt→   jump-stmt→...→id-expr→unqual-id→ident

这使得不清楚是否可以在调用其余析构函数之前复制和销毁临时文件:我会说它可能不会,因为return m_protVal;会导致块的结束,但是我在标准中找不到可以证实这一点的任何内容。

(另一方面,我没有看到任何这种行为会导致任何破坏的情况;没有人应该有一个指向临时的指针,所以它首先被销毁不是问题,如果临时有一个指针一个局部变量,当临时被销毁之后问题就出现了 - 不管在任何情况下编写这样的代码都是个好主意。)

答案 3 :(得分:1)

这是一个小型的完整测试程序(除了James Kanzes的良好解释),它将显示解锁是否在之前 之后 完成堆栈展开:

#include <iostream>

class PseudoLockGuard
{
public:
    enum LockState { IsStillLocked, IsUnlocked};

    PseudoLockGuard(LockState& value) : m_value(value) {};
    ~PseudoLockGuard() { m_value = IsUnlocked; };

private:
    LockState& m_value;
};

PseudoLockGuard::LockState Test()
{
    PseudoLockGuard::LockState indicator = PseudoLockGuard::IsStillLocked;

    PseudoLockGuard lock(indicator);

    return indicator;// Will return IsStillLocked or IsUnlocked?
}

int main(int , char** )
{
   PseudoLockGuard::LockState result = Test();

   std::cout << (result == PseudoLockGuard::IsStillLocked 
                 ? "Return Value before Unlock" 
                 : "Return Value after Unlock"); 

   // Outputs "Return Value before Unlock"

   return 0;
}