【问题标题】:Deletion of a class pointer in release build causes memory issues在发布版本中删除类指针会导致内存问题
【发布时间】:2018-01-16 16:39:43
【问题描述】:

使用/clr VS2010 编写的MFC 应用程序。多线程 DLL (/MD) 运行时库。当我将NDEBUG 的预处理器定义切换到_DEBUG 时出现问题。 NDEBUG 禁用在定义 _DEBUG 时弹出的断言。我在管理类指针的创建和删除方面做错了吗?

从 NDEBUG 切换到 _DEBUG 后,我在运行时收到“_Block_Type_Is_Valid (pHead->nBlockUse)”断言失败错误。

A 类:“A.h”

#include "B.h"

class A
{
public:
    A(void);
    ~A(void);

    A(const A&);
    A& operator=(const A&);

    B* p_B;

};

A 类:“A.cpp”

#include "StdAfx.h"
#include "A.h"

A::A(void)
{
    p_B = new B();
}

A::~A(void)
{
    delete p_B;
}

// 1. copy constructor
A::A(const A& that)
{
     p_B = new B(); 
    *p_B = *that.p_B;
}

// 2. copy assignment operator
A& A::operator=(const A& that)
{
    *p_B = *that.p_B;
    return *this;
}

B 类:“B.h”

class B
{
public:
    B(void);
    ~B(void);

    B(const B&);
    B& operator=(const B&);
};

B 类:“B.cpp”

#include "StdAfx.h"
#include "B.h"

B::B(void) { }
B::~B(void) { }

// 1. copy constructor
B::B(const B& that)
{
}

// 2. copy assignment operator
B& B::operator=(const B& that)
{
    return *this;
}

ModalDlg.cpp(实例化A类对象的地方)

BOOL CTestingReleaseBuildDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    A a;

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon

    // TODO: Add extra initialization here

    return TRUE;  // return TRUE  unless you set the focus to a control
}

然后我只是在我的 MFC 对话框中实例化 A 类,这会导致断言失败。我的问题是,“我在创建和删除类指针时做错了吗?”断言在类 A 的析构函数的“删除 p_B”指令中特别失败。

编辑: 我用BOOL CMyMFCClassDLG::OnInitDialog() { ... A a; ...}实例化A类

编辑2: 我为 A 类和 B 类定义了复制构造函数和复制赋值运算符。它们永远不会被调用。

EDIT3:值得一提的是,如果我删除 A 的析构函数中的 delete p_B; 语句,则不会再出现断言失败。

EDIT4:在定义了 /MDd 和 _DEBUG 的调试模式下,程序运行良好。当我使用 /MD 和 _DEBUG 在发布模式下运行时,断言失败。我认为这可能会导致问题,因为 /MD 可能应该与 NDEBUG 一起运行。

EDIT5:我按照@Christophe 的建议更新了代码,并插入了实例化A 类对象的函数。我不想复制/粘贴模态对话框应用程序的其余部分,但您可以通过在 VS2010 中启动一个新的基于模态对话框的 MFC 应用程序来复制确切的代码,并将项目配置更改为使用 /CLR 模式,设置运行时库到 /MD 并在预处理器定义字段中包含 _DEBUG 关键字。

EDIT6:链接到项目https://drive.google.com/drive/folders/1q0n9c6yMZ2ZKnakH6Z5NbVeGsAWfUAc1?usp=sharing

【问题讨论】:

  • 你违反了 3/5/0 的规则。
  • 你如何实例化它?如果你使用operator = (...),你就有麻烦了。
  • B 可以工作时,为什么还要使用B *
  • 断言失败的最可能原因是您多次删除同一个对象。这可能是由复制a 引起的,例如,如果您使用A a = A(); 实例化它或将a 按值传递给函数或...

标签: c++ mfc clr


【解决方案1】:

如果没有复制构造函数和赋值运算符,p_B 会从其原始 A 对象克隆。所以被销毁的两个对象中的第一个将删除p_B,第二个对象将尝试删除已经删除的指针,即UB。

在您的编辑中,您已经定义了缺失的元素。您的复制构造函数的问题,它什么都不做。所以不幸的是,复制对象的p_B 指针可能无效。你需要完成那些成员函数:

// 1. copy constructor
A::A(const A& that)
{
    p_B = new B(); 
    *p_B = *that->p_B;
}

对于复制构造函数,假设您保证 p_B 将始终指向一个有效的 B 对象,并假设不存在切片风险:

// 2. copy assignment operator
A& A::operator=(const A& that)
{
    *p_B = *that.p_B;        
    return *this;
}

如果您认为不需要复制构造函数或赋值运算符,为了确保遵守规则 3,您也可以将它们声明为已删除:

A(const A&) = delete;
A& operator=(const A&) = delete;

如果你的代码不小心使用了它们,编译器会抱怨而不是生成代码和上面提到的问题。

最后,在一个 init 函数中实例化 A。但是对于您非常短的 sn-p,它似乎是该函数的本地对象。因此,一旦您离开该功能,它就会被销毁。

【讨论】:

  • 感谢您的回复。我必须将*that->p_B; 修改为*that.p_B; 才能编译程序。我只创建了类 A 的一个实例。在这个类的构造函数中,我实例化了类 B 的一个新实例,p_B 指向该实例p_B = new B();。那么A类的复制构造函数和复制赋值操作符函数对我有什么帮助呢?它们从未被调用,因为我只通过运行A a; 实例化了 A 类的对象 a 一次。
  • 另外,忘记说了,将拷贝构造函数和拷贝赋值运算符的定义插入到A类后,程序仍然会遇到_BLOCK_TYPE_IS_INVALID(pHead->nBlockUse)断言失败。
  • 对!对不起,对于that:一种来自this 的反射;-)。如果您真的不需要这两个缺少的成员函数,请将它们声明为已删除(请参阅编辑),以让编译器避免潜在的混乱。但是根据您的代码,问题不是来自A。我很困惑:您创建了A,它分配了一个对象,但是您从不使用它,并且它的破坏导致问题在您删除对象时导致问题?然后要么指针在其他地方被删除(毕竟它是公共的),要么你在代码的恢复中出现了一些内存损坏。
  • 对,我只声明它,一旦声明对象的函数调用完成,它应该被销毁,然后问题就出现了。当我在项目配置的预处理器定义字段中包含 _DEBUG 时,会弹出断言错误。如果我改为排除该关键字(或使用 NDEBUG),那么一切正常。我根本不对 A 对象做任何其他事情。我会在原帖中放一个项目链接。
【解决方案2】:

我确定问题不是源于代码,而是源于项目配置属性。当 /MD 与 _DEBUG 配对时,似乎会出现此错误。但是,当一个项目被纳入 Release Build 时,不应该定义 _DEBUG,而应该定义 NDEBUG。一旦我将预处理器定义从 _DEBUG 更改为 NDEBUG,断言失败就不会再出现了。

值得注意的是,项目在使用 /MDd 和 _DEBUG 时执行没有问题。

【讨论】:

    猜你喜欢
    • 2021-06-14
    • 1970-01-01
    • 1970-01-01
    • 2017-01-16
    • 2011-01-07
    • 1970-01-01
    • 2013-11-22
    • 1970-01-01
    相关资源
    最近更新 更多