我想看到类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?
}
答案 0 :(得分:1)
让我们假设为了论证,所讨论的vtable确实以你认为的方式布局,作为普通内存地址的表,并且当将这些地址转换为函数指针时,它们是可调用的。 / p>
你至少有两个问题:
成员函数的调用约定不一定与普通函数相同。 Microsoft的默认调用约定是 thiscall ,它将一个指针放在ECX寄存器中调用其方法的对象上。没有手动指定的工具;实现这一目标的唯一方法是通过调用成员函数的方式调用成员函数,其中涉及obj.f()
或pobj->f()
等语法。你不能用指向函数的指针(甚至不是成员函数指针)来做到这一点,除非你编写机器代码或汇编程序以获得所有的低级细节。
你碰巧没有为func
遇到这个问题,因为它没有引用this
(直接或通过隐式引用其他成员)。但析构函数确实如此。析构函数是特殊的,实际存储在vtable中的是指向编译器生成的辅助函数的指针,该函数调用真正的析构函数,然后检查作为隐藏参数传递的一些标志,以确定它是否应该释放对象的内存。发生在ECX中的值与func
电话无关,但适合~A
电话非常重要。
析构函数与普通函数不同。如上所述,编译器可以生成一个或多个辅助函数,除了this
之外,它们还接收参数。你没有在你的代码中考虑到这一点。编译器为数组和非数组析构函数生成单独的帮助器,所以现在我们甚至不知道你在vtable的索引1找到了哪一个。但是由于你没有传递一个有效的flag参数,并且无法将this
值传递给它,所以无论如何你在vtable中找到的并不重要。
您可以通过指定不同的调用约定来尝试解决第一个问题,例如 stdcall 。这会将this
参数与其余参数一起放回堆栈,并允许您在调用函数指针时传递它。对于func
,fun
需要有这样的声明:
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一样,即在我见过的每个平台上都会爆炸。