在发布版本中删除类指针会导致内存问题

Deletion of a class pointer in release build causes memory issues

本文关键字:指针 问题 内存 删除 布版本 版本      更新时间:2023-10-16

MFC 应用程序,使用 VS2010 编写的/clr。多线程 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关键字。

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

如果没有复制构造函数和赋值运算符,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 规则,也可以将它们声明为 delete:

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

如果您的代码意外使用它们,编译器将抱怨而不是生成代码和上述问题。

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

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

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