在VC 6.0中使用函数指针调用虚拟析构函数时出错

时间:2012-05-18 08:23:49

标签: c++

我想看到类A的vtable的内容,尤其是虚拟析构函数,但我不能通过函数指针调用它。 这是我的代码:

typedef void (*fun)();


class A {
public:
     virtual func() {printf("A::func() is called\n");}
     virtual ~A() {printf("A::~A() is called\n");}
};

//enter in the vtable 
void *getvtable (void* p, int off){
     return (void*)*((unsigned int*)p+off);
}

//off_obj is used for multiple inherence(so not here), off_vtable is used to specify the   position of function in vtable
fun getfun (A* obj, unsigned int off_obj,int off_vtable){
     void *vptr = getvtable(obj,off_obj);
     unsigned char *p = (unsigned char *)vptr;
     p += sizeof(void*) * off_vtable;
     return (fun)getvtable(p,0);
}


void main() {
     A* ptr_a = new A;
     fun pfunc = getfun(ptr_a,0,0);
     (*pfunc)();
     pfunc = getfun(ptr_a,0,1);
     (*pfunc)(); //error occurred here, this is supposed to be the virtual desctrutor, why?
}

2 个答案:

答案 0 :(得分:1)

让我们假设为了论证,所讨论的vtable确实以你认为的方式布局,作为普通内存地址的表,并且当将这些地址转换为函数指针时,它们是可调用的。 / p>

你至少有两个问题:

  1. 成员函数的调用约定不一定与普通函数相同。 Microsoft的默认调用约定是 thiscall ,它将一个指针放在ECX寄存器中调用其方法的对象上。没有手动指定的工具;实现这一目标的唯一方法是通过调用成员函数的方式调用成员函数,其中涉及obj.f()pobj->f()等语法。你不能用指向函数的指针(甚至不是成员函数指针)来做到这一点,除非你编写机器代码或汇编程序以获得所有的低级细节。

    你碰巧没有为func遇到这个问题,因为它没有引用this(直接或通过隐式引用其他成员)。但析构函数确实如此。析构函数是特殊的,实际存​​储在vtable中的是指向编译器生成的辅助函数的指针,该函数调用真正的析构函数,然后检查作为隐藏参数传递的一些标志,以确定它是否应该释放对象的内存。发生在ECX中的值与func电话无关,但适合~A电话非常重要。

  2. 析构函数与普通函数不同。如上所述,编译器可以生成一个或多个辅助函数,除了this之外,它们还接收参数。你没有在你的代码中考虑到这一点。编译器为数组和非数组析构函数生成单独的帮助器,所以现在我们甚至不知道你在vtable的索引1找到了哪一个。但是由于你没有传递一个有效的flag参数,并且无法将this值传递给它,所以无论如何你在vtable中找到的并不重要。

  3. 您可以通过指定不同的调用约定来尝试解决第一个问题,例如 stdcall 。这会将this参数与其余参数一起放回堆栈,并允许您在调用函数指针时传递它。对于funcfun需要有这样的声明:

    typedef void (__stdcall * fun)(A*);
    

    像这样调用pfunc

    pfunc(ptr_a);
    

    要解决第二个问题,您需要确定vtable函数的实际顺序,以便找到正确的析构函数帮助程序。要调用它,你也需要一个不同的函数指针声明。析构函数在技术上没有返回类型,但void运行良好。你可以使用这样的东西:

    typedef void (__stdcall * destr)(A*, unsigned flags);
    

    对于大多数答案,我使用an article by Igorsk来识别程序中的某些模式,以便将其反编译回C ++。第2部分包括课程。

答案 1 :(得分:-1)

您不会调用析构函数。你调用operator delete(),它会找出析构函数。直接调用析构函数是Undefined Behavior,就像取消引用NULL一样,即在我见过的每个平台上都会爆炸。