【问题标题】:visual studio 2012 update 3 compiler bug - not calling dtorVisual Studio 2012 更新 3 编译器错误 - 不调用 dtor
【发布时间】:2013-09-05 23:44:13
【问题描述】:

我相信我在 Visual Studio 2012 Update 3 C++ 编译器中发现了一个有点晦涩但可怕的错误。我在使用 gtest 编写单元测试时发现了它。测试开始显示内存泄漏,在调查问题后似乎减少为编译器中的错误。

我将问题提交给 Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/794722/parameter-dtor-not-called-when-overloaded-operator-involved-in-return

过去,我错误地将自己的错误称为“编译器错误”,这比我愿意承认的要多。所以我想我会在这里发布这个问题,以防有人想自己重现这个问题。如果我可以在这段代码中指出我自己的错误,那将非常有帮助!我真的希望 VC++ 编译器不会在下面的程序中调用析构函数。

请注意,错误行为是在禁用优化器时发生的,因此它不是优化器错误。

我在 gcc 4.2.1 (i686-apple-darwin11) 中尝试了这段代码,它的行为符合预期。

这是项目中单个源文件的代码:

#include <string>

int instance_count= 0;

class c {
public:
    c( std::string s ) : m_s(s) { ++instance_count; }
    c( const c& other ) : m_s(other.m_s) { ++instance_count; }
    ~c() {--instance_count;}
private:
    std::string m_s;
};

class d {
public:
    d() {}
    void operator=(int) {}
};

void f( c c_ ) {
    try {}
    catch(...) { return d() = 5; }
}

int main( int argc, char* argv[] ) {
    c instance("leak");
    f(instance);
    return instance_count == 1 ? 0 : -1;
}

在 Visual Studio 2012 Update 3 中编译它:

  1. 文件 -> 新建 -> 项目...,选择 Win32 控制台应用程序,单击确定,然后单击完成
  2. Build -> Configuration Manager -> Active Solution Platform -> New...,选择x64,点击OK
  3. 用上面的代码替换主.cpp文件的内容
  4. 将#include "stdafx.h" 添加到文件顶部或关闭预编译头文件
  5. 运行程序,注意退出代码是 -1,我希望它是 0。这似乎在 32 位和 64 位版本中重现,尽管我专注于 64 位。
  6. 注释掉 f() 中的 try/catch 块,注意退出代码变为 0。我不明白为什么这个更改会影响退出代码,因为 catch() 块甚至没有执行。李>

【问题讨论】:

  • 过于优化,无法进行调用。看起来代码优化器被 f() 优化而不再接受参数。等待反馈报告跟进。
  • 实际上这发生在优化器禁用的情况下 - 我编辑了问题以包含重要信息。
  • 可能不相关,但我不确定return d() = 5; 是否应该首先编译。您实际上返回了赋值运算符生成的 void 值。调用者本身确实返回了void,但在这种情况下使用return 对我来说看起来很奇怪。
  • 这确实是难题的一部分。去掉 return 关键字,问题就消失了。这是一个代码生成错误,它在 try{} 之后计算错误的地址以继续,跳过参数的 ~c 析构函数调用。看起来它被 catch 子句代码迷惑了。
  • 我同意它看起来很神秘。它实际上来自 Google 的 gtest 单元测试框架中的 FAIL() 语句。他们在帮助类中使用这个重载的 operator=() 出于某种我不清楚的原因。我确实认为它是有效的 C++。我记得在 msvc6 天,从返回 void 的函数返回 void 表达式无法编译,这让我抓狂!

标签: visual-c++


【解决方案1】:

看起来像是 codegen 中的一个问题。反汇编显示函数 f 如下。

带有return语句 -

    try { }
002039B8  mov         byte ptr [ebp-4],1  
002039BC  jmp         f+6Eh (02039DEh)  
    catch(...) { return d() = 5; }
002039BE  push        5  
002039C0  lea         ecx,[ebp-0D5h]  
002039C6  call        d::d (0201474h)  
002039CB  mov         ecx,eax  
002039CD  call        d::operator= (0201479h)  
002039D2  mov         eax,2039E7h  
002039D7  ret  
002039D8  mov         eax,2039DEh  
002039DD  ret  
$LN4:
002039DE  mov         dword ptr [ebp-4],0  
002039E5  jmp         $LN8+0Fh (02039F6h)  
$LN8:
002039E7  mov         dword ptr [ebp-4],0FFFFFFFFh  
002039EE  lea         ecx,[c_]  
002039F1  call        c::~c (020101Eh)  
}

注意反汇编 try 块的跳转 f+6Eh(02039DEh)。这跳转到

002039DE  mov         dword ptr [ebp-4],0  
002039E5  jmp         $LN8+0Fh (02039F6h)  

这完全跳过了对析构函数的调用。还要注意的另一件事是对析构函数的调用右大括号('}')之前。

如果我们看一下没有返回语句的代码,

    try { }
013839B8  mov         byte ptr [ebp-4],1  
013839BC  jmp         f+68h (013839D8h)  
    catch(...) { /*return*/ d() = 5; }
013839BE  push        5  
013839C0  lea         ecx,[ebp-0D5h]  
013839C6  call        d::d (01381474h)  
013839CB  mov         ecx,eax  
013839CD  call        d::operator= (01381479h)  
013839D2  mov         eax,13839E1h  
013839D7  ret  
013839D8  mov         dword ptr [ebp-4],0  
013839DF  jmp         $LN8+7h (013839E8h)  
$LN8:
013839E1  mov         dword ptr [ebp-4],0  
}
013839E8  mov         dword ptr [ebp-4],0FFFFFFFFh  
013839EF  lea         ecx,[c_]  
013839F2  call        c::~c (0138101Eh)

这里,对析构函数的调用是大括号('}')之后。

【讨论】:

  • 感谢您对错误的完整分析。对我来说这似乎是一个非常严重的问题,但是 VC++ 编译器团队目前决定不修复这个错误(您可以在我的问题中的 MSDN 链接中查看他们的 cmets)!
猜你喜欢
  • 2013-06-29
  • 2017-11-25
  • 2013-04-09
  • 1970-01-01
  • 2015-04-12
  • 2012-12-28
  • 1970-01-01
  • 2017-06-19
  • 1970-01-01
相关资源
最近更新 更多