You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++虚表与析构函数问题:派生类数组delete[]崩溃分析

数组delete[]基类指针的未定义行为分析

以下C++代码执行delete[]时会触发段错误,且崩溃前未调用任何析构函数:

class X
{
public:
    virtual ~X() {}
    
private:
    double m_a;
};

class Y: public X
{
private:
    int m_b;
};

int main()
{
    X *xp = new Y[5];
    delete[] xp;

    return 0;
}

核心问题:未定义行为的触发

你遇到的崩溃本质是用基类指针delete[]派生类数组触发了C++98标准中的未定义行为——C++明确要求,delete[]的指针类型必须与new分配的数组元素类型完全匹配,数组场景下即使基类有虚析构函数,也不允许用基类指针销毁派生类数组。

虚表偏移量的计算逻辑

虚表的偏移量完全由编译期确定的固定索引决定,和函数名无关。每个虚函数在类的虚表中都有唯一的索引位置:比如基类X的虚析构函数是X虚表的第0个条目(假设是第一个虚函数)。派生类Y的虚表会继承基类的虚表结构,并用自己的虚函数覆盖对应索引的条目——这里Y的虚表第0个条目是~Y的地址,而非~X。

你观察到的崩溃不是因为虚表偏移计算错误,而是数组delete[]的本质问题:当用X*指向Y数组时,编译器会按X的对象大小来计算数组元素的步长(比如X占16字节,就每次+16),但实际Y的对象更大(比如24字节),导致后续“伪对象”的起始地址根本不是有效Y对象的位置,访问这些地址的虚表指针自然会触发段错误,而且因为地址无效,连析构函数都没机会调用。

疑问解答

1. 把Y的m_b改为double后程序不崩溃?

这是未定义行为的“侥幸”表现:当m_b改为double时,Y的大小可能刚好和X的大小形成某种内存布局巧合,比如在你的编译环境中,Y的大小是X的整数倍,或者错误步长访问的位置刚好是一段可访问的内存(虽然不是有效对象的虚表指针,但没触发段错误)。但这依然是未定义行为,换个环境或编译器版本可能还是会崩溃。

2. X的析构改为非虚时程序不崩溃?

当析构函数非虚时,delete[]会直接调用X的析构函数(而非通过虚表查找),且X的析构是空操作。此时即使编译器按X的步长遍历数组,也不会去访问虚表指针——既没有无效内存访问,也没有复杂的虚函数调用逻辑,所以不会触发段错误。但这依然是未定义行为,比如如果X/Y有需要释放的资源,会导致内存泄漏。

内容的提问来源于stack exchange,提问作者Alon Kalif

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 21:13:17