代码之家  ›  专栏  ›  技术社区  ›  IanH

如果删除一个对象,会发生什么(gcc)(当double delete崩溃时?)

  •  6
  • IanH  · 技术社区  · 14 年前

    如果删除对象并使用gcc作为编译器,具体会发生什么?

    上周我正在调查一个崩溃,在这个崩溃中,竞争条件导致了一个对象的双重删除。

    调用对象的虚拟析构函数时发生崩溃,因为指向虚拟函数表的指针已被覆盖。

    我想知道为什么我之前没有意识到这个问题,唯一的解释是,要么虚拟函数表在第一次删除过程中立即被覆盖,要么第二次删除没有崩溃。

    (第一种情况意味着,如果发生“争用”,崩溃总是发生在同一个位置——第二种情况是,争用发生时通常不会发生任何事情——并且只有在问题发生的同时第三个线程覆盖了delete对象。)


    编辑/更新:

    我做了一个测试,以下代码因segfault(gcc4.4、i686和amd64)崩溃:

    class M
    {
    private:
      int* ptr;
    public:
      M() {
      ptr = new int[1];
      }
      virtual ~M() {delete ptr;}
    };
    
    int main(int argc, char** argv)
    {
      M* ptr = new M();
      delete ptr;
      delete ptr;
    }
    

    对于“virtual”,在对析构函数进行间接函数调用时会发生崩溃,因为指向virtual函数表的指针无效。

    在amd64和i686上,指针都指向一个有效的内存区域(堆),但那里的值无效(计数器?它非常低,例如0x11或0x21),因此“call”(或编译器执行返回优化时的“jmp”)跳转到无效区域。

    分段错误。 0x0000000021

    在??()(gdb公司)

    # 0 0x0000000000000021英寸()

    #

    因此,在上述条件下,指向虚函数表的指针总是被第一次删除所覆盖,因此如果类具有虚析构函数,则下一次删除将跳转到nirvana。

    3 回复  |  直到 9 年前
        1
  •  6
  •   user283145 user283145    14 年前

    它非常依赖于内存分配器本身的实现,更不用说任何依赖于应用程序的故障,比如覆盖某个对象的v-table。有许多内存分配器方案,它们在功能和对double free()的抵抗力上都有所不同,但它们都有一个共同的特性:应用程序在第二次free()之后的某个时间会崩溃。

    崩溃的原因通常是内存分配器在每个分配的内存块之前(页眉)和之后(页脚)专门使用少量内存来存储一些特定于实现的细节。头文件通常定义块的大小和下一个块的地址。页脚通常是指向块头的指针。删除两次通常至少需要检查相邻的块是否空闲。因此,如果出现以下情况,程序将崩溃:

    1) 指向下一个区块的指针已被覆盖,第二个free()在尝试访问下一个区块时会导致segfault。

    2) 上一个区块的页脚已被修改,访问上一个区块的页眉会导致segfault。

    如果应用程序存活下来,这意味着free()在不同的位置有损坏的内存,或者将添加与一个已经可用的块重叠的可用块,从而在将来导致数据损坏。最终,您的程序将在涉及损坏的内存区域的以下free()或malloc()之一处出错。

        2
  •  6
  •   anon anon    14 年前

        3
  •  1
  •   user195488 user195488    14 年前

    通过执行 delete 两次(甚至 free ),内存可能已经被重新分配并通过执行 删除 再次可能导致内存损坏。分配的内存块的大小通常保持在内存块本身之前。

    如果有派生类,则不要对派生类(子类)调用delete。如果未声明为虚拟,则只有 ~BaseClass() DerivedClass 坚持和泄漏。这假设 派生类 BaseClass 必须释放。

    BaseClass* obj_ptr = new DerivedClass;  // Allowed due to polymorphism.
    ...
    delete obj_ptr;  // this will call the destructor ~Parent() and NOT ~Child()
    
    推荐文章