尝试使用类型转换的指针调用析构函数时,我的代码出现分段错误。但是如果我将析构函数更改为非虚拟的,它就可以正常工作。
#include <iostream>
using namespace std;
class Test
{
public:
Test() { cout << "Cons" << endl;}
~Test() {cout << "Des"<<endl;}
void *var_ptr;
};
class Test3
{
public:
Test3() { cout << "Cons3" << endl;}
//virtual ~Test3(){cout << "Des3" << endl;};
~Test3(){cout << "Des3" << endl;};
};
class Test2:public Test3
{
public:
Test2() { cout << "Cons2" << endl;}
~Test2() {cout << "Des2"<<endl;}
};
int main ()
{
Test *testPtr = new Test();
int *ivalue ;
ivalue = new int;
testPtr->var_ptr = (void*)ivalue;
((Test2*)(testPtr->var_ptr))->~Test2();
}
OutPut :
Cons
Segmentation fault
Without virtual dtor :
Output :
Cons
Des2
Des3
Run Code Online (Sandbox Code Playgroud)
就我的代码而言,我无法避免使用类型转换指针 (Test2*)。我想了解为什么在使用 virtual destructor 时会出现段错误。以及是否有任何其他方式对指针进行类型转换以使其正确。
问题是您的代码触发了Undefined behavior,这意味着几乎任何事情都可能发生。该程序无效,崩溃是一种选择,因为不崩溃只是兼容编译器可以对您的代码执行的操作的另一种选择。你应该做的是纠正你的程序。
只是为了理解行为的原因,而不是试图暗示您可以将其用于任何事情:未定义的行为是undefined,这意味着您永远不能依赖它。作为对正在发生的事情的实际解释,您可以使用以下代码复制类似类型的情况:
struct test1 {
void f() { printf( "Hi\n" ); }
};
struct test2 {
int x;
void f() { printf( "%d\n", x); }
};
int main() {
static_cast<test1*>(0)->f(); // probably won't crash
static_cast<test2*>(0)->f(); // probably will crash
}
Run Code Online (Sandbox Code Playgroud)
这个无效程序在概念上与您的相似,不同之处在于在这种情况下,指针不指向任何有效内存,而是一个空指针。该行为的实际解释与(大多数)编译器如何处理代码有关。当你定义一个成员函数时,编译器生成一个普通函数的等价物,其中第一个参数是一个指向对象的指针,其余的参数在后面。每当访问成员属性时,都会解除对指针的引用并读取另一端的值,但如果未使用任何成员,则编译器可能根本不会解除对指针的引用。上面的代码将在内部编译为类似于以下 C 代码的内容:
struct test1 {};
void test1_f( struct test1* this ) {
printf( "Hi\n" );
}
struct test2 { int x; }
void test2_f( struct test2* this ) {
printf( "%d\n", p->x );
}
Run Code Online (Sandbox Code Playgroud)
在第一种情况下,根本没有使用指针,所以即使它是一个空指针,因为它没有被取消引用,代码似乎可以工作(它仍然无效,应该更正,但在这个特定的实现中它会不崩溃)。在第二种情况下,指针使用,以访问构件x,以及将尝试读取围绕虚拟地址0,这将触发分段错误,程序将崩溃存储器。
回到最初的问题,当你声明一个虚函数时,编译器(不是强制的,但所有编译器都会这样做)会为层次结构中的每个类型创建一个虚拟表,并且它会为对象添加一个隐藏的指针字段指的是特定类型的虚拟表。C++代码:
struct test {
virtual void f() { printf( "Hi\n" ); }
};
int main() {
static_cast<test*>(0)->f();
}
Run Code Online (Sandbox Code Playgroud)
被翻译成相当于(为简单起见,假设编译器在这里使用动态分派,考虑到 0,实际上是对返回 NULL 的函数的调用,test*因此编译器在不知道类型的情况下必须使用动态分派) :
struct test {
void (**__vptr)(); // hidden vptr
}
test_test(struct test* this) { // constructor
this->__vptr = &__test_vtable;
}
void test_f( struct test* this ) {
printf( "Hi\n" );
}
void (*__test_vtable)()[] = { &test_f }; // type is slightly off here
int main() {
((struct test*)(0)->__vptr)[ 0 ]( (struct test*)0 );
}
Run Code Online (Sandbox Code Playgroud)
从上到下,类型的实际结构被布局,包括指向虚拟表的隐藏指针。构造函数是隐式创建的,并将 的值设置__vptr为适当的表。在main指针中,首先取消引用以获得最终覆盖程序的地址,然后调用该地址处的函数0作为this指针传递。请注意,即使test_f没有取消引用指针,调用者也已经尝试取消引用访问vtable,这会触发分段错误和崩溃。
最后,在您的情况下,虚拟方法不仅仅是任何方法,而是析构函数,这增加了编写等效 C 代码的一些复杂性,事实上,根据 ABI,编译器将生成多个析构函数,但是前一个例子中显示的与虚f函数相同的问题将触发,程序将崩溃。
| 归档时间: |
|
| 查看次数: |
1822 次 |
| 最近记录: |