双删除会发生什么?

时间:2012-02-07 01:20:06

标签: c++ pointers memory-leaks

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

当您意外双重删除时,最糟糕的情况是什么?有关系吗?编译器会抛出错误吗?

8 个答案:

答案 0 :(得分:22)

未定义的行为。标准没有任何保证。可能你的操作系统提供了一些保证,比如“你不会破坏另一个进程”,但这对你的程序没什么帮助。

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

答案 1 :(得分:21)

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

答案 2 :(得分:17)

这是未定义的行为,因此实际结果会因编译器而异。运行环境。

在大多数情况下,编译器不会注意到。在许多(如果不是大多数)情况下,运行时内存管理库将崩溃。

在幕后,任何内存管理器都必须维护一些关于它分配的每个数据块的元数据,以允许它从malloc / new返回的指针中查找元数据。通常,这采用在分配块之前的固定偏移处的结构的形式。这个结构可以包含一个“幻数” - 一个不太可能通过纯粹机会发生的常数。如果内存管理器在预期的位置看到幻数,它知道提供给free / delete的指针很可能是有效的。如果它没有看到幻数,或者它看到不同的数字意味着“此指针最近被释放”,它可以默默地忽略空闲请求,或者它可以打印有用的消息并中止。根据规范,这两种方法都是合法的,并且两种方法都有赞成/反对意见。

如果内存管理器没有在元数据块中保留幻数,或者没有检查元数据的完整性,那么任何事情都可能发生。根据内存管理器的实现方式,结果很可能是没有有用消息的崩溃,无论是在内存管理器逻辑中,还是在下次内存管理器尝试分配或释放内存时稍晚,或者更晚以后当程序的两个不同部分都认为他们拥有相同的内存块时。

我们来试试吧。将代码转换为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运行时实际上检测到它是双重删除,并且在它崩溃之前非常有用。

答案 3 :(得分:4)

编译器可能会发出警告或其他内容,特别是在显而易见的情况下(例如在您的示例中),但它始终无法检测到。 (你可以使用像valgrind这样的东西,它在运行时可以检测到它)。至于行为,它可以是任何东西。一些安全的库可能会检查并处理它 - 但其他运行时(速度)将使您调用的假设是正确的(它不是)然后崩溃或更糟。允许运行时假设你不是双重删除(即使双重删除会做坏事,例如崩溃你的计算机)

答案 4 :(得分:1)

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

从C ++常见问题解答:访问this link

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

例如,以下是灾难:

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

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

答案 5 :(得分:1)

每个人都已经告诉过你,你不应该这样做,并且它会导致未定义的行为。这是众所周知的,所以让我们在较低的层面详细说明这一点,让我们看看究竟发生了什么。

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

没有任何通用答案的原因是,由于这是未定义的,它可能因编译器而异,甚至可能在同一编译器的不同版本之间。

但这是大多数情况下“大致”发生的事情:

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 ***

直接回答您的问题:

可能发生的最糟糕的事情

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

在实践中,它很可能只是因为段错误而崩溃。但可能会发生更糟糕的事情。

编译器是否会抛出错误

不应该。

答案 6 :(得分:1)

虽然这是未定义的:

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!

这是明确定义的:

$(function(){
    $( "#tenth_DOB" ).datepicker({ 
        dateFormat: 'dd-mm-yy',             
        changeMonth: true,
        changeYear: true,
        modal: false,
        yearRange: "1947:2016",
        onSelect: function(dateStr) {
        }
    });

    $("#tenth_DateofIssue").datepicker({
        changeMonth: true,
        changeYear: true,
        maxDate: -1,
        dateFormat: 'dd-mm-yy',
        yearRange: "-20:+0",
        modal: false,
        onSelect: function(dateStr) {
        }
    });
});

我认为我应该发布它,因为没有人提到它。

答案 7 :(得分:0)

在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;
}