双删除会发生什么?

Obj *op = new Obj; Obj *op2 = op; delete op; delete op2; // What happens here? 

当你不小心加倍删除时,会发生什么情况? 有关系吗? 编译器会抛出一个错误吗?

它会导致未定义的行为。 任何事情都可能发生。 在实践中,运行时崩溃可能是我所期望的。

未定义的行为。 标准没有任何保证。 也许你的操作系统做了一些保证,比如“你不会损坏另一个进程”,但是这对你的程序不是很有帮助。

你的程序可能会崩溃。 您的数据可能已损坏。 您的下一笔薪水的直接存款可能会从您的帐户中拿出500万美元。

这是未定义的行为,所以实际结果将根据编译器和运行时环境而变化。

在大多数情况下,编译器不会注意到。 在许多情况下,即使不是大多数,运行时内存pipe理库也会崩溃。

在底层,任何内存pipe理器都必须维护它分配的每个数据块的一些元数据,这样就可以从malloc / new返回的指针中查找元数据。 典型地,这在分配块之前采取固定偏移处的结构forms。 这个结构可以包含一个“幻数” – 一个不可能由纯粹的机会发生的常量。 如果内存pipe理器在预期的地方看到幻数,它就知道提供释放/删除的指针是最有效的。 如果它看不到幻数,或者它看到一个不同的数字,意思是“这个指针最近被释放了”,它可以默默地忽略这个空闲的请求,也可以打印一个有用的消息并中止。 在规范下,要么是合法的,要么两者都有亲/争论。

如果内存pipe理器不在元数据块中保留一个幻数,或者不检查元数据的完整性,则可能发生任何事情。 根据内存pipe理器的实现方式,结果很可能是没有有用信息的崩溃,或者直接在内存pipe理器逻辑中,稍后在内存pipe理器尝试分配内存或释放内存的时候,或者晚得多,当程序的两个不同部分都认为他们拥有同一块内存。

我们来试试吧。 把你的代码转换成一个完整的so.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 10.6.8上使用gcc 4.2.1,但是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运行时间实际上检测到它是一个双重删除,并在崩溃之前相当有帮助。

编译器可能会给出一个警告或一些东西,特别是在显而易见的(如你的例子),但是它不可能总是检测。 (你可以使用valgrind之类的东西,虽然在运行时可以检测到它)。 至于行为,可以是任何事情。 一些安全的图书馆可能会检查,并处理好 – 但其他运行时(速度)将使你所调用的假设是正确的(这是不正确的),然后崩溃或更糟。 允许运行时假设你不是双重删除(即使双重删除会做一些不好的事情,例如崩溃你的计算机)

不,删除同一个指针两次是不安全的。 根据C ++标准是未定义的行为。

从C ++ FAQ:访问这个链接

删除同一个指针两次是否安全?
没有! (假设你没有从中间得到新的指针)

例如,以下是一个灾难:

 class Foo { /*...*/ }; void yourCode() { Foo* p = new Foo(); delete p; delete p; // DISASTER! // ... } 

那第二次删除p行可能会对你做一些非常糟糕的事情。 根据月亮的相位,它可能会破坏你的堆,崩溃你的程序,对堆中已经存在的物体进行任意和奇怪的改变等。不幸的是,这些症状可能随机出现和消失。 根据墨菲定律,在最糟糕的时刻(当客户正在寻找,高价值交易试图发布等时),您将遭受最严重的打击。 注意:一些运行时系统将保护你免受某些非常简单的双重删除的情况。 根据具体情况,如果您碰巧在其中一个系统上运行,并且没有人将您的代码部署到另一个处理不同事情的系统上,并且如果删除了没有析构函数的东西,在两次删除之间没有做任何重要的事情,如果没有人在两次删除之间更改代码以执行重要的操作,并且您的线程调度程序(您可能无法控制!)不会发生交换线程两个删除,如果,如果,如果。 所以回到墨菲:因为它可能出错,它会,而且会在最糟糕的时刻出错。 非崩溃不能certificate没有错误; 它只是不能certificate存在的错误。 相信我:双删除是坏的,坏的,坏的。 拒绝吧。

每个人都已经告诉过你,你不应该这样做,这将导致不确定的行为。 这是众所周知的,所以让我们在较低的层面上进行详细阐述,让我们看看究竟发生了什么。

普遍的答案是任何事情都可能发生,这并不完全正确。 例如,计算机不会试图杀死你这样做(除非你正在为机器人编程AI):)

之所以不能有任何普遍的答案,是因为这是未定义的,它可能会有所不同,从编译器到编译器,甚至在同一编译器的不同版本之间。

但这在大多数情况下是“粗略”发生的:

delete包含2个主要操作:

  • 它调用析构函数,如果它被定义
  • 它以某种方式释放分配给对象的内存

所以,如果你的析构函数包含任何访问任何已经被删除的类的数据的代码,那么它可能会出现段错误或(很可能)你会读取一些无意义的数据。 如果这些被删除的数据是指针,那么它很可能是段错误,因为你将尝试访问包含其他内容的内存,或者不属于你。

如果您的构造函数没有触及任何数据或者不存在(为简单起见,我们不考虑虚拟析构函数),但这可能不是大多数编译器实现中崩溃的原因。 但是,调用析构函数并不是将要发生的唯一操作。

内存需要被释放。 如何完成取决于在编译器中的实现,但也可以执行一些free函数,给它的指针和大小的对象。 在已被删除的内存上调用free可能会崩溃,因为内存可能不再属于您。 如果它属于你,它可能不会立即崩溃,但它可能会覆盖已经分配给你的程序的一些不同的对象的内存。

这意味着你的一个或多个内存结构已经被破坏,你的程序可能迟早会崩溃,或者它的行为可能会非常奇怪。 原因在你的debugging器中不会很明显,你可能花了好几个星期来弄清楚到底发生了什么。

所以,正如其他人所说,这通常是一个坏主意,但我想你已经知道了。 不要担心,如果你删除一个对象两次,天真的小猫很可能不会死亡。

这是错误的示例代码,但也可能工作得很好(它可以在Linux上运行GCC):

 class a {}; int main() { a *test = new a(); delete test; a *test2 = new a(); delete test; return 0; } 

如果在删除之间不创build该类的中间实例,则按预期发生的两次对同一内存释放的调用:

 *** Error in `./a.out': double free or corruption (fasttop): 0x000000000111a010 *** 

直接回答你的问题:

什么是可能发生的最坏情况

理论上,你的程序会导致一些致命的事情。 它甚至可能会在一些极端情况下随机尝试擦除硬盘。 机会取决于你的程序实际是什么(内核驱动程序?用户空间程序?)。

在实践中,它很可能会碰到segfault。 但是可能会发生更糟的事情

编译器会抛出一个错误吗?

它不应该。

虽然这是未定义的:

 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! 

以为我应该张贴它,因为没有人提到它。