调用派生 class 方法时出现分段错误
Segmentation fault when calling derived class method
我有一个与设计带有数组参数的派生 classes 相关的问题。我有 class B 从 A 派生。class BB 从 AA 派生,分别有 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。
如何解决这个问题?
谢谢。
请注意 cc->getA()
在语义上等于 cc->A::getA()
(不是 cc->B::getA()
)并且 returns 是指向 A
的指针(而不是 B*
).
现在,由于A
是B
的子类,但后者还包含一些额外的字段,那么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] 而导致未定义的行为,其中规定:
For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar ([conv.qual]), the behavior is undefined. [ Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. — end note ]
您可能想要类似于以下的行为:
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 个问题:
- 由于您的 classes 负责内存管理,我建议创建您的析构函数
virtual
,因为如果您在任何时候尝试删除派生的 class object via base pointer, derived classes 的析构函数将不会被调用。这在您的 当前 代码中应该不是问题,但将来可能会成为问题。
即:
int main ()
{
AA* aa = new BB (2);
delete aa;
}
在你的情况下不会调用 BB::~BB()
。
- 您注意到的问题,并写下这个问题。
将类型变量从 BB*
转换为 AA*
后(即使转换不是必需的,您也可以直接赋值,因为类型是协变的) :
AA* cc = dynamic_cast<AA*>(bb);
您的变量 cc
被视为类型 AA*
(在一般情况下,它具有 BB*
的运行时类型并不重要 - 您不需要'知道,并且不应该关心确切的运行时类型)。在任何虚拟方法调用中,它们都会通过使用 vtable 分派到正确的类型。
现在,为什么在 console/segmentation 错误中打印出奇怪的值? cc->getA ()
的结果是什么?由于变量 cc
被视为 AA*
,因此 return 值为 A*
(如上所述,实际类型为 B*
,但是,由于是-继承关系被视为 A*
)。有什么问题,你可能会问:数组 m_a
在这两种情况下大小相同,对吧?
嗯,不是真的,为了解释这一点,我需要解释数组索引在 C++ 中的工作原理,以及它与对象大小的关系。
我想,我不会让你震惊,说明类型 B
(sizeof (B)
) 的对象的大小大于类型 A
(sizeof (A)
), 因为 B
拥有 A
的所有东西(由于继承),还有它自己的一些东西。在我的机器上 sizeof(A)
= 16 字节,sizeof(B)
= 28 字节。
因此,当您创建一个数组时,该数组占用的总量 space 是 [element_count] * [size of the element]
字节,这似乎是合乎逻辑的。但是,当你需要从数组中取出一个元素时,它需要计算出该元素在内存中的确切位置,在该数组占用的所有 space 中,因此它通过计算它。它按如下方式执行:[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 ()
得到处理as 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)`,不幸的是,它位于保留给对象的 space 中间,然而,任何值都可以 printed/anything 发生 - undefined行为被调用。
如何解决?我会通过实现虚拟索引运算符来修复它,而不是 classes 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";
我有一个与设计带有数组参数的派生 classes 相关的问题。我有 class B 从 A 派生。class BB 从 AA 派生,分别有 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。 如何解决这个问题? 谢谢。
请注意 cc->getA()
在语义上等于 cc->A::getA()
(不是 cc->B::getA()
)并且 returns 是指向 A
的指针(而不是 B*
).
现在,由于A
是B
的子类,但后者还包含一些额外的字段,那么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] 而导致未定义的行为,其中规定:
For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar ([conv.qual]), the behavior is undefined. [ Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. — end note ]
您可能想要类似于以下的行为:
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 个问题:
- 由于您的 classes 负责内存管理,我建议创建您的析构函数
virtual
,因为如果您在任何时候尝试删除派生的 class object via base pointer, derived classes 的析构函数将不会被调用。这在您的 当前 代码中应该不是问题,但将来可能会成为问题。
即:
int main ()
{
AA* aa = new BB (2);
delete aa;
}
在你的情况下不会调用 BB::~BB()
。
- 您注意到的问题,并写下这个问题。
将类型变量从 BB*
转换为 AA*
后(即使转换不是必需的,您也可以直接赋值,因为类型是协变的) :
AA* cc = dynamic_cast<AA*>(bb);
您的变量 cc
被视为类型 AA*
(在一般情况下,它具有 BB*
的运行时类型并不重要 - 您不需要'知道,并且不应该关心确切的运行时类型)。在任何虚拟方法调用中,它们都会通过使用 vtable 分派到正确的类型。
现在,为什么在 console/segmentation 错误中打印出奇怪的值? cc->getA ()
的结果是什么?由于变量 cc
被视为 AA*
,因此 return 值为 A*
(如上所述,实际类型为 B*
,但是,由于是-继承关系被视为 A*
)。有什么问题,你可能会问:数组 m_a
在这两种情况下大小相同,对吧?
嗯,不是真的,为了解释这一点,我需要解释数组索引在 C++ 中的工作原理,以及它与对象大小的关系。
我想,我不会让你震惊,说明类型 B
(sizeof (B)
) 的对象的大小大于类型 A
(sizeof (A)
), 因为 B
拥有 A
的所有东西(由于继承),还有它自己的一些东西。在我的机器上 sizeof(A)
= 16 字节,sizeof(B)
= 28 字节。
因此,当您创建一个数组时,该数组占用的总量 space 是 [element_count] * [size of the element]
字节,这似乎是合乎逻辑的。但是,当你需要从数组中取出一个元素时,它需要计算出该元素在内存中的确切位置,在该数组占用的所有 space 中,因此它通过计算它。它按如下方式执行:[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 ()
得到处理as 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)`,不幸的是,它位于保留给对象的 space 中间,然而,任何值都可以 printed/anything 发生 - undefined行为被调用。
如何解决?我会通过实现虚拟索引运算符来修复它,而不是 classes 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";