_crtisvalidheappointer(block) 删除基础 class 指针时

_crtisvalidheappointer(block) when deleting base class pointer

抱歉这么久 post,但我需要帮助才能理解这一点。

我有下面的代码抛出 _crtisvalidheappointer(block)

#include <iostream>
#include <string>
using namespace std;

#define ASSERT_ERROR
/*#define SOLVE_ASSERT*/

class Player
{
    std::string playerName;
public:
    Player(std::string &playerName) :playerName(playerName) { cout << "Player Constructed\n"; }
    void printPlayerName() const
    {
        std::cout << playerName << endl;
    }
#if defined SOLVE_ASSERT
    virtual ~Player() { cout << "Player Destructed\n"; }
#else
    ~Player() { cout << "Player Destructed\n"; }
#endif
};
#if defined ASSERT_ERROR
class Batsman : virtual public Player
#else
class Batsman : public Player
#endif
{
public:
    Batsman(std::string playerName) : Player(playerName) { cout << "Batsman info added\n"; }
    ~Batsman() { cout << "Batsman Destructed\n"; }
};

int main()
{
    Player *ptr = new Batsman("Sachin Tendulkar");
    ptr->printPlayerName();
    delete ptr;
}

一旦我取消注释下面的行,我就解决了这个问题。

#define SOLVE_ASSERT

我在下面提到 link 那里说单继承不会导致问题。

但在我的例子中,我只是使用 virtual 来继承 class,这有点要求我使析构函数成为虚拟的。为什么会这样?

为什么下面这行代码在没有虚拟继承的情况下是合法的,为什么在虚拟继承的情况下不合法?

Player *ptr = new Batsman(playerName);

在我开始之前,这是我用来调试此代码的稍微修改过的版本:

#include <iostream>
#include <string>
using namespace std;

class Player {
    std::string playerName;
public:
    Player(std::string &playerName) :playerName(playerName) { 
        cout << "Player Constructed @" << this << endl; 
    }
    
    void printPlayerName() const {
        std::cout << playerName << endl;
    }
    
    ~Player() { 
        cout << "Player Destructed @" << this << endl; 
    }

};

class Batsman : virtual public Player {
public:
    Batsman(std::string playerName): 
        Player(playerName)
    { 
        cout << "Batsman info added @" << this << endl; 
    }
    
    ~Batsman() { 
        cout << "Batsman Destructed @" << this << endl; 
    }
};

int main() {
    Player *ptr = new Batsman("Sachin Tendulkar");
    ptr->printPlayerName();
    delete ptr;
}

如果我们尝试 运行 您当前状态下的代码,我们会得到以下输出消息(尽管指针值可能会有所不同):

Player Constructed @0x1048c68                                                                                                                                           
Batsman info added @0x1048c60                                                                                                                                           
Sachin Tendulkar                                                                                                                                                        
Player Destructed @0x1048c68                                                                                                                                            
*** Error in `./a.out': free(): invalid pointer: 0x0000000001048c68 ***                                                                                                 
Aborted (core dumped)

如果我们查看指针值,我们会注意到内存在错误的位置被释放。对于另一个例子,请考虑以下代码:

int* arr = new int[100];
cout << "Array is @" << arr << endl << "Memory being freed is @" << (arr+50) << endl;
delete[] (arr+50);

如果我们 运行 这个,我们得到的输出与我们在你的例子中得到的输出非常相似:

Array is @0xd8cc20                                                                                                                                                      
Memory being freed is @0xd8cce8                                                                                                                                         
*** Error in `./a.out': free(): invalid pointer: 0x0000000000d8cce8 ***                                                                                                 
Aborted (core dumped)

我们收到此错误是因为 0xd8cc200xd8cce8 之间的 200 个字节实际上并未被释放,从而造成内存泄漏。回到您的程序,我们注意到 0x1048c600x1048c68 之间的字节没有被释放。为了解决这个问题,我们必须确保调用正确的析构函数,我们可以通过以下两种方式之一来实现:

  1. 我们可以将 ptr 声明为 Batsman* 而不是 Player*。这会 告诉编译器调用 Batsman 析构函数而不是 Player 构造函数,成功释放所有内存。
  2. 我们可以使 ~Player() 函数 virtual。这表明 编译器将确定要调用的正确函数 运行时间。如果这样做, Batsman 析构函数将被调用,即使 尽管该对象被声明为指向 Player.
  3. 的指针

编辑:以下是为什么从 class Batsman : virtual public Player 中删除 virtual 似乎可以修复错误的解释:

当您使用 virtual 继承时,子类将在 超类之前 分配内存。因此,超类开始分配其内存的时间点将在对象实际启动之后很久。这解释了为什么在您的示例中,Batsman 对象在 Player 之前分配了 8 个字节。当 free() 尝试使用超类地址解除分配时,它会发现那里没有分配对象,从而导致错误。

同时,在正常的继承中,子类和超类都会从同一个地址开始分配。这意味着您可以使用超类地址释放内存。由于在超类的地址分配了一个指针,C++ 内存管理系统就好像它是一个有效的释放一样。