正在删除动态分配的内存

Deleting dynamically allocated memory

本文关键字:内存 动态分配 删除      更新时间:2023-10-16

我在动态内存分配方面面临一些概念性问题。首先,如果我写以下代码

int *p = NULL;
delete p;

为什么我没有出错?我正在尝试删除(堆栈上)没有指向任何内容的指针。如果我写以下语句

int *p = new int;
p = NULL;
delete p;

我再次没有得到编译或运行时错误。为什么?继续,如果我写下面的代码,我会得到一个运行时错误

int *p = new int;
p = NULL;
delete p;
delete p;

为什么?如果我写下面的代码,我不会得到错误

int *p = NULL;
delete p;
delete p;

为什么?有人能从概念上解释这背后的原因吗?

我假设在您的第三个示例中,您打算编写

int *p = new int;
delete p;
delete p;

从形式上讲,这会导致未定义的行为,这意味着任何事情都可能发生。在实践中,您可能使用了一个内存分配器来检查您正在删除的指针是否指向其空闲内存池中。

其他人已经指出,从定义上讲,删除空指针不会导致错误,所以不管你做了多少次。

将空指针传递给delete运算符是不可能的。标准是这么说的:

5.3.5/2

在备选方案[delete and delete[]中,如果delete的操作数的值是空指针,则该操作无效。


考虑一个拥有指向另一个对象的指针的对象。通常,当拥有对象的析构函数运行时,它会通过删除它来清理拥有对象的内存。但在拥有对象也可能为null的情况下,我们该如何清理内存?一种选择是将每个删除都包装在"if(X)delete X"类型的包装器中。但这太吵了,没有任何实际的好处。因此,delete操作符会为您执行此操作。

"我正在尝试删除(堆栈上)没有指向任何东西的指针。"

这不是真的。不能从堆栈中删除。使用delete可以删除堆上地址存储在指针中的内存块。指针本身是一个堆栈变量。

在任何情况下,您都只删除一个null指针,根据定义,它总是"安全的",因为它是一个无操作(C++标准明确规定了这一点)。

在第二个和第三个示例中,在删除指针之前,您将向指针重新分配一个新值(nullpointer),这意味着您正在泄漏以前分配的整数。这是通常不应该发生的事情(在这种情况下,你不会因为泄露一个整数而死亡,但这不是一件好事)。

第三个和第四个示例中的双重删除通常是严重的编程错误,但在您的示例中它们是"无害的",因为删除的指针是nullpointer(因此这是一个非操作)。


有点偏离轨道:
注意,我把"安全"answers"无害"放在上面的引号里是有充分理由的。我个人不同意斯特劳斯特鲁普先生的设计决定
将删除null指针设为"无害的无操作"实际上不是一个好主意,即使其意图可能很好。Stroustrup先生甚至更进一步,允许delete将指针设置为null,并表示他希望实现真的做到了这一点(幸运的是,我所知道的实现都没有做到!)。

在我看来,每一个被分配的对象都应该被删除一次,不能少也不能多。

一个行为良好、未损坏的程序可以(也必须)删除指针的时间和频率是精确定义的,它不是随机的未知事物。删除必须只发生一次,程序必须完全意识到这一点,因为它必须确定对象是否有效(因为如果对象无效,使用它是非法的!)。

在删除对象后设置指向nullpointer的指针将导致随后取消引用已删除对象时出错(这是一件好事),但它确实可以防止双重删除。相反,它隐藏这个严重的编程错误,而忽略它。

如果一个程序删除一个指针两次,那么程序逻辑就被破坏了,它就不能正常工作。这不是一件可以忽略的事情,它必须被修复。因此,这样的程序应该崩溃。分配器通常检测双重删除,但通过将指针重置为null指针,可以有效地禁用这种检测机制。

如果在删除指针后选择重置指针,则应该(在我看来)将其设置为无效的非null指针值,例如(T*)1(T*)-1。这将保证取消引用和删除指针都会在第一次出现故障。

没有人喜欢看到程序崩溃。但是,与不正确的程序逻辑持续不确定的时间,并可能在随机情况下崩溃或静默地损坏数据相比,提前崩溃并在第一次崩溃是一件好事

我认为,如果你试图删除指针,你实际上是在删除指针指向的对象在内存中的位置。你可以使用参考:

int *p = NULL;
delete &p;

内部实现对我们程序员来说是透明的。正如您所看到的,deleteNULL指针可能是无害的,但通常应该避免这种情况。您可能看到过类似"请不要重新删除动态指针"的文字