访问虚拟功能时应用程序崩溃

问题描述:

下面是示例代码

Below is the sample code

#include "conio.h"
#include "iostream"

using namespace std;

class CBase
{
public:
    int m_nBaseData;
    virtual void Display() = 0;
};

class CDerived : public CBase
{
public:
    int m_nDerivedData;
    void Display()
    {
        cout<<"CDerived::Display() : "<<m_nBaseData<<endl;
    }
};

void main()
{
    CBase* ptr = new CDerived[2];
    ptr[0].m_nBaseData = 10;
    ptr[1].m_nBaseData = 20;
    ptr[0].Display();
    ptr[1].Display();
    getch();
}



如果我删除成员变量int m_nDerivedData;从CDerived类中提取,则不会发生应用程序崩溃.
有人可以解释吗?



If I remove member variable int m_nDerivedData; from CDerived class then application crash does not occur.
Can anyone explain this?

您为什么希望该代码起作用?数组和指针是完全不同的野兽,您正试图互换使用它们.

您正在做的是:

-创建两个派生类对象的数组(每个对象是一个v表,两个int长)

-您正在告诉编译器,指向这些对象中第一个的指针实际上是指向基类对象的指针(每个对象是一个v表,一个int长)

-然后您要告诉编译器它实际上是至少2个派生类对象的数组.

-要引用第二个对象,编译器将从您告诉它的第一个对象的起始位置开始偏移一个v-table和一个int的偏移量开始读取(大多数编译器的内存布局将以此作为起始位置)未初始化的派生类的成员'')

-编译器从第一个对象(即未初始化的内存)的后端读取其认为的v-table指针,对其进行解除引用,查找显示函数并...
-程序开始爆炸

所以这里的道德是:

-切勿通过指向其他类型的指针访问数组

-不要让数据成员保持未初始化状态-如果您在派生类成员变量中添加了已知模式,则可能会为您提供线索.

如果您想做某种事情,那么您将需要一个助手类来做.取消引用为派生类但返回基类指针的东西.

干杯,

Ash
Why would you expect that code to work? Arrays and pointers are completely different beasts and you''re trying to use them interchangeably.

What you''re doing is:

- creating an array of two derived class objects (each object is a v-table and two ints long)

- you''re telling the compiler that the pointer to the first of these objects is actually a pointer to a base class object (each object is a v-table and one int long)

- you''re then telling the compiler it''s actually an array of at least 2 derived class objects.

- to reference the second object the compiler starts reading at an offset of one v-table and one int from the start of where you''ve told it the first object is (with most compiler''s memory layout that will be the start of the uninitialised derived class'' member)

- the compiler reads what it thinks the v-table pointer is from the back end of the first object, which is uninitialised memory, dereferences it, looks up the display function and...

- program go bang

So the morals here are:

- Never access arrays through pointers to a different type

- Don''t leave data members uninitialised - if you''d stuck a known pattern in your derived class member variable it might have given you a clue as to what was going on.

If you want to do the sort of thing you''re trying you''re going to need a helper class to do it. Something that dereferences as the derived class but returns a base class pointer.

Cheers,

Ash


Aescleal对出现问题的描述非常好.

由于未提及此特定代码的解决方案,因此我想在这里添加它.

Aescleal''s description of what goes wrong is excellent.

Since the solution for this particular piece of code wasn''t mentioned however, I thought I''d add it here.

CBase* ptr = new CDerived[2];



应该是



should be

CDerived* ptr = new CDerived[2];


Aescleal是正确的,这是对实现问题的公正描述.

我想您想看看它如何工作.

Aescleal is right, that''s a fair description of what''s wrong with the implementation.

I guess you would like to see how it could be made to work though.

#include "conio.h"
#include "iostream"
using namespace std;
class CBase
{
public:
    int m_nBaseData;
    virtual ~CBase() {}
    virtual void Display() = 0;
};

class CDerived :
    public CBase
{
public:
    int m_nDerivedData;
    void Display()
    {
        cout<<"CDerived::Display() : "<<m_nBaseData<<endl;
    }
};


void main()
{
    CBase* ptr[2] = {new CDerived(),new CDerived()};

    CBase* ptr1 = ptr[0];
    CBase* ptr2 = ptr[1];

    ptr1->m_nBaseData = 10;
    ptr2->m_nBaseData = 20;

    ptr1->Display();
    ptr2->Display();

    getch();

    delete ptr1;
    delete ptr2;

}



ptr是在堆栈上分配的指向CBase的两个指针的数组-无需删除它.
实例是动态分配的,因此在退出之前将其删除是一种好习惯.

我猜想您希望能够通过CBase指针在两个实例上调用Display()函数,这很好.也可以这样编码:



ptr is an array of two pointers to CBase allocated on the stack - no need to delete it.
The instances are allocated dynamically, so deleting them before we exit is good practice.

I guess you wanted to be able to call the Display() function on both instances through your CBase pointers, well this does the trick. It could also be coded like this:

ptr[0]->Display();
ptr[1]->Display();



因此,您的初始代码并不是那么遥不可及,Aescleal为您提供了合理的解释,以说明错误之处.

我还向CBase添加了虚拟析构函数virtual ~CBase() {}.

我觉得这是开发类层次结构时的一种好习惯,因为它允许您在派生类中实现清除逻辑,即使在指向父类的指针上调用delete,派生类也将在销毁类中执行.
问候
Espen Harlinn



So your inital code wasn''t that far off, and Aescleal gave you a well reasoned explaination on what was wrong with it.

I also added a virtual destructor virtual ~CBase() {} to CBase.

I feel that this is a good practice when developing class hierarchies, since it allows you to implement cleanup logic in derived classes that will be executed during destruction, even if delete is called on a pointer to a parent class.

Regards
Espen Harlinn