双重删除中会发生什么
What happens in a double delete?
Obj *op = new Obj;
Obj *op2 = op;
delete op;
delete op2; // What happens here?
当您不小心重复删除时可能发生的最坏情况是什么?有关系吗?编译器会抛出错误吗?
它会导致未定义的行为。 任何事情都可能发生。 在实践中,运行时崩溃可能是我所期望的。
未定义的行为。 该标准没有任何保证。 可能您的操作系统会做出一些保证,例如"您不会损坏另一个进程",但这对您的程序没有多大帮助。
您的程序可能会崩溃。 您的数据可能已损坏。 直接存入您的下一份薪水可能会从您的帐户中扣除 500 万美元。
这是未定义的行为,因此实际结果将根据编译器和运行时环境而有所不同。
在大多数情况下,编译器不会注意到。在许多情况下(如果不是大多数(情况下,运行时内存管理库将崩溃。
在后台,任何内存管理器都必须维护有关其分配的每个数据块的一些元数据,以允许它从 malloc/new 返回的指针中查找元数据。通常,这采用在分配块之前具有固定偏移量的结构形式。这种结构可以包含一个"幻数"——一个不太可能纯粹偶然发生的常数。如果内存管理器在预期位置看到幻数,它知道提供给 free/delete 的指针很可能有效。如果它没有看到幻数,或者如果它看到一个表示"此指针最近被释放"的其他数字,它可以静默地忽略免费请求,也可以打印有用的消息并中止。根据规范,两者都是合法的,并且任何一种方法都有赞成/反对的论据。
如果内存管理器没有在元数据块中保留幻数,或者没有以其他方式检查元数据的健全性,那么任何事情都可能发生。根据内存管理器的实现方式,结果很可能是没有有用消息的崩溃,要么立即在内存管理器逻辑中,要么在下次内存管理器尝试分配或释放内存时稍晚一点,要么在程序的两个不同部分各自认为它们拥有同一块内存时,又远又远。
让我们试试吧。将您的代码转换为完整的程序.cpp以便:
class Obj
{
public:
int x;
};
int main( int argc, char* argv[] )
{
Obj *op = new Obj;
Obj *op2 = op;
delete op;
delete op2;
return 0;
}
编译它(我在 OSX 4.2.1 上使用 gcc 10.6.8,但 YMMV(:
russell@Silverback ~: g++ so.cpp
运行它:
russell@Silverback ~: ./a.out
a.out(1965) malloc: *** error for object 0x100100080: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap
看看那里,gcc 运行时实际上检测到它是双重删除,并且在崩溃之前相当有帮助。
虽然这是未定义的:
int* a = new int;
delete a;
delete a; // same as your code
这是明确定义的:
int* a = new int;
delete a;
a = nullptr; // or just NULL or 0 if your compiler doesn't support c++11
delete a; // nothing happens!
以为我应该发布它,因为没有其他人提到它。
编译器可能会发出警告或其他内容,尤其是在明显的情况下(如您的示例(,但它不可能始终检测到。(您可以使用像valgrind这样的东西,它在运行时可以检测到它(。至于行为,它可以是任何东西。一些安全的库可能会检查并处理它 - 但其他运行时(为了速度(会假设你调用是正确的(事实并非如此(,然后崩溃或更糟。允许运行时假设您没有双重删除(即使双重删除会做一些不好的事情,例如使您的计算机崩溃(
每个人都已经告诉你,你不应该这样做,它会导致未定义的行为。这是众所周知的,所以让我们在较低的层面上详细说明这一点,让我们看看实际发生了什么。
标准的普遍答案是任何事情都可能发生,这并不完全正确。例如,计算机不会试图杀死你这样做(除非你正在为机器人编程人工智能(:)
不可能有任何通用答案的原因是,由于这是未定义的,因此它可能因编译器而异,甚至在同一编译器的不同版本之间也可能有所不同。
但在大多数情况下,这就是"大致"发生的事情:
delete
由 2 个主要操作组成:
- 如果已定义,则调用析构函数
- 它以某种方式释放分配给对象的内存
因此,如果您的析构函数包含访问已删除的类的任何数据的任何代码,则可能会出现段错误,或者(很可能(您将读取一些无意义的数据。如果这些删除的数据是指针,那么它很可能会出现段错误,因为您将尝试访问包含其他内容或不属于您的内存。
如果您的构造函数不接触任何数据或不存在(为了简单起见,我们不要在这里考虑虚拟析构函数(,则在大多数编译器实现中,这可能不是崩溃的原因。但是,调用析构函数并不是此处将要发生的唯一操作。
内存需要释放。它是如何完成的取决于编译器中的实现,但它也可以执行一些free
函数,给它指针和对象的大小。在已删除的内存上调用free
可能会崩溃,因为内存可能不再属于您。如果它确实属于您,它可能不会立即崩溃,但它可能会覆盖已经为程序的某些不同对象分配的内存。
这意味着您的一个或多个内存结构刚刚损坏,您的程序迟早可能会崩溃,或者它的行为可能非常奇怪。原因在调试器中并不明显,您可能需要花费数周时间弄清楚刚刚发生了什么。
所以,正如其他人所说,这通常是一个坏主意,但我想你已经知道了。不过别担心,如果您删除一个对象两次,无辜的小猫很可能不会死亡。
这是错误的示例代码,但也可以正常工作(它在 Linux 上的 GCC 上工作正常(:
class a {};
int main()
{
a *test = new a();
delete test;
a *test2 = new a();
delete test;
return 0;
}
如果我在删除之间没有创建该类的中间实例,则在同一内存上释放的 2 次调用将按预期发生:
*** Error in `./a.out': double free or corruption (fasttop): 0x000000000111a010 ***
要直接回答您的问题:
可能发生的最坏情况是什么:
从理论上讲,您的程序会导致致命的东西。在某些极端情况下,它甚至可能会随机尝试擦除硬盘驱动器。机会取决于你的程序实际上是什么(内核驱动程序?用户空间程序?(。
在实践中,它很可能只是因段错误而崩溃。但更糟糕的事情可能会发生。
编译器会抛出错误吗?
不应该。
不,删除同一指针两次是不安全的。根据C++标准,它是未定义的行为。
从C++常见问题解答:访问此链接
删除同一指针两次是否安全?
不!(假设您没有从中间的新指针返回该指针。
例如,下面是灾难:
class Foo { /*...*/ };
void yourCode()
{
Foo* p = new Foo();
delete p;
delete p; // DISASTER!
// ...
}
第二个删除p行可能会对你做一些非常糟糕的事情。根据月相的不同,它可能会损坏您的堆,使您的程序崩溃,对堆上已经存在的对象进行任意和奇怪的更改等。不幸的是,这些症状可能会随机出现和消失。根据墨菲定律,你会在最糟糕的时刻受到最严重的打击(当客户在看的时候,当一个高价值的交易试图发布时,等等(。注意:某些运行时系统会保护您免受某些非常简单的双重删除情况的影响。根据细节,如果您碰巧在其中一个系统上运行,并且没有人将您的代码部署到另一个以不同方式处理事情的系统上,并且如果您要删除没有析构函数的内容,并且如果您在两次删除之间没有做任何重要的事情,并且没有人更改您的代码以在两次删除之间执行重要操作,并且如果您的线程调度程序(您可能无法控制!(不会碰巧在两个删除以及 if、if和 if 之间交换线程。所以回到墨菲:既然它可能出错,它就会出错,而且它会在最糟糕的时刻出错。非崩溃并不能证明没有错误;它只是无法证明错误的存在。相信我:双重删除很糟糕,不好,不好。只要说不。
在 GCC 7.2、Clang 7.0 和 Zapcc 5.0 上,它在第 9 次删除时中止。
#include <iostream>
int main() {
int *x = new int;
for(size_t n = 1;;n++) {
std::cout << "delete x " << n << " time(s)n";
delete x;
}
return 0;
}
- 当我从下面的代码中删除关键字 virtual 时,它可以正常工作,否则会出现错误。在这里"virtual"字的意义是什么?
- 如果我真的真的想从 STL 容器继承,并且我继承构造函数并删除新运算符,会发生什么?
- 我正在尝试使用 while 循环从字符串中删除字母,直到没有字母。我在这里做错了什么?
- 我什么时候会默认(而不是删除)基类中的复制和移动操作
- 删除对象(具有不同类型)的引用时会发生什么情况?
- 在析构函数中删除单链表的正确方法是什么?
- 处理从列表中删除指向对象的指针的"healthy"方法是什么?
- 将此私有删除器函数包装在结构中的目的是什么?
- 删除类成员的动态分配内存的最佳方法是什么
- 除了调用全局删除运算符之外,删除一个void指针还能做什么呢
- 在这种情况下,删除指针数组期间会发生什么?
- 为什么你会=删除隐式删除的默认构造函数,有什么意义?
- 当删除或删除[]被执行时,计算机会做什么?
- 如果在删除后使用 delete[] 会发生什么?
- 擦除remove_if成语 - 删除了什么吗?
- 如果我不删除动态创建的变量会发生什么
- 删除数组成员的更好方法是什么?
- 删除默认类构造函数有什么意义?
- 定义无删除指针的最干净方法是什么
- 使用动态指针数组进行动态对象分配 - 使用什么删除?