请注意,我不想解决我的问题的任何问题 - 我正在考虑事情发生的概率,因此想知道一些事情:
如果您删除对象并使用 gcc 作为编译器,究竟会发生什么?
上周我正在调查一次崩溃,其中竞争条件导致对象的双重删除。
调用对象的虚析构函数时发生崩溃,因为指向虚函数表的指针已经被覆盖。
虚函数指针是否被第一次删除覆盖?
如果不是,那么第二次删除是否安全,只要同时没有进行新的内存分配?
我想知道为什么我之前没有识别出我遇到的问题,唯一的解释是虚拟函数表在第一次删除期间被立即覆盖,或者第二次删除没有崩溃。
(第一个意味着如果“比赛”发生,崩溃总是发生在同一个位置 - 第二个,当比赛发生时通常什么都不会发生 - 并且只有在第三个线程覆盖删除对象时才会发生问题。 )
编辑/更新:
我做了一个测试,以下代码因段错误(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)所以编译器时的“调用”(或“jmp”做了返回优化)跳转到无效区域。
程序接收信号 SIGSEGV,
分段故障。0x0000000000000021
在 ??() (gdb)
#
0 0x0000000000000021 在?? ()
#
1 0x000000000040083e 在 main ()
所以在上面提到的条件下,指向虚函数表的指针总是被第一次删除覆盖,所以如果类有虚析构函数,下一次删除将跳转到必杀技。