调用派生类方法时出现分段错误

时间:2016-05-05 11:22:57

标签: c++ class pointers segmentation-fault derived

我遇到了使用数组参数设计派生类的问题。我有来自A的B类和来自AA的B类,分别是B和A的数组......

#include <iostream>

class A
{
public:
    A(){}
    virtual void foo(){std::cout<<"foo A\n";}
    int idx[3];
};

class B: public A
{
public:
    B():A(){}
    void foo(){std::cout<<"foo B\n";}
    int uidx[3];
};

class AA
{
public:
    AA(){}
    AA(int count){
        m_count = count;
        m_a = new A[count];
    }
    virtual A* getA(){return m_a;}
    ~AA(){ delete[] m_a;}
protected:
    A* m_a;
    int m_count;
};

class BB: public AA
{
public:
    BB(int count):AA()
    {
        m_count = count;
        m_a = new B[count];
    }
    B* getA(){return dynamic_cast<B*>(m_a);}
};

int main()
{
    AA* aa = new AA(2);
    BB* bb = new BB(2);
    B* b = bb->getA();
    B& b0 = *b;
    b0.idx[0] = 0;
    b0.idx[1] = 1;
    b0.idx[2] = 2;

    B& b1 = *(b+1);
    b1.idx[0] = 2;
    b1.idx[1] = 3;
    b1.idx[2] = 4;

    std::cout<<bb->getA()[1].idx[0]<<"\n"; //prints 2
    std::cout<<bb->getA()[1].idx[1]<<"\n"; //prints 3
    std::cout<<bb->getA()[1].idx[2]<<"\n"; //prints 4

    AA* cc = static_cast<AA*>(bb);
    cc->getA()[0].foo();  //prints foo B

    std::cout<<cc->getA()[1].idx[0]<<"\n"; //prints 4198624 ??
    std::cout<<cc->getA()[1].idx[1]<<"\n"; //prints 0 ??
    std::cout<<cc->getA()[1].idx[2]<<"\n"; //prints 2 ??

    cc->getA()[1].foo();  //segmentation fault
    delete aa;
    delete bb;
    return 0;
}

在将BB静态转换为AA后,我无法访问指数大于0的A。 如何解决这个问题? 谢谢。

2 个答案:

答案 0 :(得分:1)

请注意,cc->getA()在语义上等于cc->A::getA()(不是cc->B::getA()),并返回指向A的指针(而不是B*)。

现在,由于AB的子类,但后者还包含一些额外的字段,然后是sizeof(B) > sizeof(A)。由于cc->getA()[n]基本上是*(cc->getA() + n)

cc->getA()[1].foo();

做同样的事情:

A * const tmp = cc->getA();
A & tmp2 = *(tmp + 1); // sizeof(A) bytes past tmp
tmp2.foo();

由于C ++标准的§5.7.6[expr.add]而导致未定义的行为:

  

对于加法或减法,如果表达式P或Q具有类型“指向cv T的指针”,其中T和数组元素类型不相似([conv.qual]),则行为未定义。 [ 注意: 特别是,当数组包含派生类类型的对象时,指向基类的指针不能用于指针算术。 - 结束记录 ]

您可能希望行为类似于以下内容:

A * const tmp = cc->getA();
A & tmp2 = *(static_cast<B *>(tmp) + 1); // sizeof(B) bytes past tmp
tmp2.foo();

为此你需要使用类似的东西:

std::cout<<static_cast<B*>(cc->getA())[1].idx[0]<<"\n"; // prints 2
std::cout<<static_cast<B*>(cc->getA())[1].idx[1]<<"\n"; // prints 3
std::cout<<static_cast<B*>(cc->getA())[1].idx[2]<<"\n"; // prints 4

static_cast<B*>(cc->getA())[1].foo();  // prints foo B

但是,最好为A & operator[](std::size_t)实施虚拟AA运算符,并在BB中覆盖它。

答案 1 :(得分:0)

我可以在您的代码中看到2个问题:

  1. 由于你的类负责内存管理,我建议你做一个析构函数virtual,因为如果你在任何时候尝试通过基指针删除派生类对象,派生类的析构函数将会不被援引。它不应该是当前代码中的问题,但可能会成为未来的问题。
  2. 即:

    int main ()
        {
        AA* aa = new BB (2);
        delete aa;
        }
    

    在您的情况下不会致电BB::~BB()

    1. 您注意到的问题,并写下这个问题。
    2. 将类型变量从BB*转换为AA*后(即使不需要强制转换,您可以直接分配,因为类型是协变的) :

      AA* cc = dynamic_cast<AA*>(bb);
      

      您的变量cc被视为类型AA*(它的运行时类型为BB*并不重要,一般情况下 - 您不要#39;知道,并且不应该关心确切的运行时类型)。在任何虚方法调用中,通过使用vtable将它们分派到正确的类型。

      现在,为什么在控制台/分段故障中打印出奇怪的值? cc->getA ()的结果是什么?由于变量cc被视为AA*,因此返回值为A*(如上所述,实际类型为B*,但由于is-a继承关系被视为A*)。您可能会问:问题是什么:在这两种情况下,数组m_a的大小都相同,对吧?

      嗯,不是真的,要解释一下,我需要解释数组索引在C ++中是如何工作的,以及它与对象大小的关系。

      我想,我不会让你感到震惊,说明Bsizeof (B))类型的对象大小大于A类型的对象({{ 1}}),因为sizeof (A)包含B所拥有的一切(由于继承),以及它自己的一些东西。在我的机器上A = 16个字节,sizeof(A) = 28个字节。

      因此,在创建数组时,数组占用的空间总量为sizeof(B)个字节,这似乎是合乎逻辑的。但是,当你需要从一个数组中获取一个元素时,它需要确定该元素在内存中在数组占用的所有空间中的确切位置,所以它通过计算它来实现。它的作用如下:[element_count] * [size of the element]

      而且,现在我们找到了问题的根源。您正在尝试[start of the array] + [index] * [size of element],但是,由于cc->getA ()[1]cc,因此BB*变量的大小为AA::m_a({{ 1}}在我的机器上;第一个对象从偏移2 * sizeof (B)开始(= 2 * 28 = 56;第二个偏移00 * sizeof (B))),但自28被处理后as 1 * sizeof(B) 1 * sizeof(A)`,遗憾的是,它位于保留给对象的空间的中间,然而,任何值都可以打印/任何事情都可能发生 - 调用未定义的行为。

      如何解决?我会通过在类cc->getA () / A*, and you are trying to fetch second element from the array (index 1), it tries to fetch the object from the offset of上实现虚拟索引运算符而不是GetA方法来解决此问题,如下所示:

      AA

      但是,那么你需要小心地在对象本身上调用操作符,而不是指向对象的指针:

      BB