如果删除一个对象到底会发生什么? (gcc)(当双删除崩溃时?)
请注意,我不想解决我的问题的任何问题 - 我正在考虑事情发生的概率,因此想知道一些事情:
如果删除对象并使用 gcc 作为编译器,到底会发生什么?< /strong>
上周我正在调查一次崩溃,其中竞争条件导致对象被双重删除。
调用对象的虚拟析构函数时发生崩溃,因为指向虚拟函数表的指针已被覆盖。
虚函数指针是否被第一次delete覆盖了?
如果不是,那么只要同时不进行新的内存分配,第二次删除是否安全?
我想知道为什么我遇到的问题之前没有被识别出来,唯一的解释是虚拟函数表在第一次删除期间立即被覆盖,或者第二次删除不会崩溃。
(第一个意味着如果发生“竞争”,崩溃总是发生在同一位置 - 第二个意味着当竞争发生时通常什么也不会发生 - 并且只有当第三个线程同时覆盖删除对象时)
编辑/更新:
我做了一个测试,以下代码因段错误而崩溃(gcc 4.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;
}
如果我从 dtor 中删除“虚拟” ,程序被 glibc 中止,因为它检测到双重释放。 对于“虚拟”,当对析构函数进行间接函数调用时会发生崩溃,因为指向虚拟函数表的指针无效。
在 amd64 和 i686 上,指针都指向有效的内存区域(堆),但那里的值无效(计数器?它非常低,例如 0x11 或 0x21),因此编译器时的“call”(或“jmp”)做了返回优化)跳转到无效区域。
程序收到信号SIGSEGV,
分段错误。 0x0000000000000021
在?? () (gdb)
#
0 0x0000000000000021 在 ?? ()
#
1 0x000000000040083e 在 main() 中
因此,有了上述条件,指向虚函数表的指针总是会被第一次删除覆盖,所以如果类有的话,下一次删除将跳转到必杀技虚拟析构函数。
Please note that I don't want to solve any problem with my question - I was thinking about probabilities of things to happen and thus was wondering about something:
What exactly happens if you delete on object and use gcc as compiler?
Last week I was investigating a crash, where a race condition lead to an double delete of an object.
The crash occured when calling the virtual destructor of the object, because the pointer to the virtual function table already was overwritten.
Is the virtual function pointer overwritten by the first delete?
If not, is the second delete safe then, as long as no new memory allocation is made in the meantime?
I'm wondering why the problem I had was not recognized before and the only exlanation is that either the virtual function table is overwritten immediatly during the first delete or the second delete does not crash.
(The first means that the crash always occurs on the same location if the "race" happens - the second one, that usually nothing happens when the race happens - and only if a third thread overwrites the delete object in the meantime the problem occurs.)
Edit/Update:
I did a test, the following code crashes with a segfault (gcc 4.4, i686 and 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;
}
If I remove the 'virtual' from the dtor, the program is aborted by glibc because it detects the double-free.
With the 'virtual' the crash occurs when doing the indirect function call to the destructor, because the pointer to the virtual function table is invalid.
On both amd64 and i686 the pointer points to a valid memory region (heap), but the value there is invalid (a counter? It's very low, e.g 0x11, or 0x21) so the 'call' (or 'jmp' when the compiler did a return-optimization) jumps to an invalid region.
Program received signal SIGSEGV,
Segmentation fault.
0x0000000000000021in ?? () (gdb)
#
0 0x0000000000000021 in ?? ()
#
1 0x000000000040083e in main ()
So with the above mentioned conditions, the pointer to the virtual function table is ALWAYS overwritten by the first delete, so the next delete will jump to nirvana if the class has a virtual destructor.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
它非常依赖于内存分配器本身的实现,更不用说覆盖某些对象的 v-table 等任何应用程序相关的故障。有许多内存分配器方案,所有这些方案在功能和对双重 free() 的抵抗方面都有所不同,但所有这些方案都有一个共同的属性:您的应用程序将在第二次 free() 之后的某个时间崩溃。
崩溃的原因通常是内存分配器在每个分配的内存块之前(页眉)和之后(页脚)专用少量内存来存储一些特定于实现的细节。标头通常定义块的大小和下一个块的地址。页脚通常是指向块头的指针。删除两次通常至少涉及检查相邻块是否空闲。因此,如果出现以下情况,您的程序将崩溃:
1)指向下一个块的指针已被覆盖,并且第二个 free() 在尝试访问下一个块时导致段错误。
2) 前一个 chunk 的页脚已被修改,访问前一个 chunk 的 header 会导致段错误。
如果应用程序存活下来,则意味着 free() 在各个位置损坏了内存,或者将添加与已空闲块之一重叠的空闲块,从而导致将来的数据损坏。最终,您的程序将在涉及损坏的内存区域的以下 free() 或 malloc() 之一处出现段错误。
It is very dependent on the implementation of the memory allocator itself, not to mention any application dependent failures as overwritting v-table of some object. There are numerous memory allocator schemes all of which differ in capabilities and resistance to double free() but all of these share one common property: your application will crash at some time after the second free().
The reason for the crash is usually that memory allocator dedicates small amount of memory before(header) and after(footer) each allocated chunk of memory to store some implementation specific details. Header usually defines size of the chunk and address of the next chunk. Footer is usually pointer to the header of the chunk. Deleting twice usually at least involves checking if adjacent chunks are free. Thus your program will crash if:
1) pointer to the next chunk has been overwritten and the second free() causes segfault when trying to access the next chunk.
2) the footer of the previous chunk has been modified and access to the header of the previous chunk causes segfault.
If the application survives, it means that free() has either corrupted memory in various locations or will add free chunk which overlaps one of already free chunks, leading to data corruption in the future. Eventually your program will segfault at one of following free() or malloc() involving the corrupted memory areas.
两次删除某些内容是未定义的行为 - 您不需要任何进一步的解释,而且寻找这种行为通常是徒劳的。它可能会导致程序崩溃,也可能不会,但这样做总是一件坏事,并且在完成它之后程序将始终处于未知状态。
Deleting something twice is undefined behaviour - you don't need any further explanation than that, and it is generally fruitless to look for one. It might cause a program to crash, it might not, but it is always a bad thing to do and the program will always be in an unknown state after you have done it.
通过执行两次
delete
(甚至free
),内存可能已经被重新分配,再次执行delete
可能会导致内存损坏。分配的内存块的大小通常保存在内存块本身之前。如果您有派生类,请勿对派生类(子类)调用删除。如果未将其声明为虚拟,则仅调用
~BaseClass()
析构函数,从而使DerivedClass
中分配的内存保留并泄漏。这假设DerivedClass
在必须释放的BaseClass
之上分配了额外的内存。IE
By executing
delete
twice (or evenfree
), the memory may already be reallocated and by executingdelete
again may cause memory corruption. The size of the block of memory allocated is often held just before the memory block itself.If you have a derived class, do not call delete on the derived class (child). If it is not declared virtual then only the
~BaseClass()
destructor is called leaving any allocated memory from theDerivedClass
to persist and leak. This assumes that theDerivedClass
has extra memory allocated above and beyond that of theBaseClass
which must be freed.i.e.