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



我有一个与使用数组参数设计派生类有关的问题。我有从 A 派生的 B 类。和类 BB 派生自 AA,分别带有 B 和 A 数组......

#include <iostream>
class A
{
public:
    A(){}
    virtual void foo(){std::cout<<"foo An";}
    int idx[3];
};
class B: public A
{
public:
    B():A(){}
    void foo(){std::cout<<"foo Bn";}
    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。如何解决这个问题?谢谢。

请注意,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

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

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

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

即:

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

在您的情况下不会打电话给BB::~BB()

  1. 你注意到的问题,并写下这个问题。

将类型的变量从 BB* 强制转换为 AA* 后(即使不需要强制转换,也可以直接赋值,因为类型是协变的(:

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

您的变量cc被视为 AA* 类型(一般来说,它的运行时类型 BB* 并不重要 - 您不知道,也不应该关心确切的运行时类型(。在任何虚拟方法调用中,它们都会通过使用 vtable 被调度到正确的类型。

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

好吧,不是真的,为了解释这一点,我需要解释数组索引在C++中的工作原理,以及它与对象大小的关系。

我想,我不会让你感到震惊,说 B 型 (sizeof (B)( 的对象的大小大于 A 型 (sizeof (A) (,因为B拥有A拥有的一切(由于继承(,还有一些自己的东西。在我的机器上,sizeof(A) = 16 字节,sizeof(B) = 28 字节。

因此,当您创建数组时,该数组占用的总空间量为 [element_count] * [size of the element] 字节,这似乎是合乎逻辑的。但是,当你需要从数组中获取一个元素时,它需要计算出该元素在内存中确切的位置,在该数组占用的所有空间中,因此它通过计算它来做到这一点。它这样做如下:[start of the array] + [index] * [size of element] .

现在我们找到了问题的根源。您正在尝试执行cc->getA ()[1],但是,由于cc,在引擎盖下,是BB*的,所以AA::m_a变量的大小是2 * sizeof (B)(在我的机器上= 2 * 28 = 56;第一个对象从偏移量0开始(0 * sizeof (B);第二个对象从偏移量28(1 * sizeof(B)(开始(,但由于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 1 * sizeof (A(', 不幸的是,它位于为对象保留的空间中间,但是,可以打印任何值/任何事情都可能发生 - 调用未定义的行为。

如何解决?我会通过实现虚拟索引运算符来修复它,而不是在类 AA/BB 上实现GetA方法,如下所示:

class AA
    {
    public:
        ...
        virtual A& operator[] (int idx)
            {
            return m_a[idx];
            }
        ...
    };
class BB : public AA
    {
    public:
        ...
        virtual B& operator[] (int idx)
            {
            return dynamic_cast<B*>(m_a)[idx];
            }
        ...
    };

但是,您需要小心地在对象本身上调用运算符,而不是指向对象的指针:

std::cout << cc->operator[](1).idx[0] << "n";
std::cout << cc->operator[](1).idx[1] << "n";
std::cout << cc->operator[](1).idx[2] << "n";

相关内容

  • 没有找到相关文章

最新更新