下面这个例子,读起来可能有些晦涩,但是读懂了对于理解C++的函数调用约定、函数指针、动态绑定、虚函数表 和 多态机制有极大的帮助。
测试环境:Windows+vs,Linux+GCC
成员函数在Windows下的默认的函数调用约定为__thiscall,这种约定会把this指针的值直接放到寄存器ECX中,而不是将其入参数栈。当采用常规的成员函数调用方式的时候,ECX会被填入正确的this值,但是我们通过普通的函数指针来调用成员函数的时候,ECX的值是未知的,故此采用__cdecl方式来将其编译。
#include <iostream>
class Base {
public:
Base() { std::cout << "Base()" << std::endl; }
virtual ~Base() { std::cout << "~Base()" << std::endl; }
virtual void
#ifdef _WIN32
__cdecl
#endif
echo(int i) { std::cout << "Base::echo(" << i << ")" << std::endl; }
};
class Derived : public Base {
public:
Derived() : Base() { std::cout << "Derived()" << std::endl; }
virtual ~Derived() { std::cout << "~Derived()" << std::endl; }
virtual void
#ifdef _WIN32
__cdecl
#endif
echo(int i) { std::cout << "Derived::echo(" << i << ")" << std::endl; }
};
int main(int argc, char* argv[]) {
typedef void (
#ifdef _WIN32
__cdecl
#endif
*TFunction )(void*, int);
Derived d;
Base* p = &d;
// On many platforms, std::size_t is synonymous with std::uintptr_t.
size_t* vptr = reinterpret_cast<size_t*>(p);
size_t* vtable = reinterpret_cast<size_t*>(*vptr);
#ifdef _WIN32
TFunction invoker = reinterpret_cast<TFunction>(vtable[1]);
#else
TFunction invoker = reinterpret_cast<TFunction>(vtable[2]);
#endif
invoker(p, 1);
}
值得指出的是,C++标准上从来没有规定虚表指针放在对象的什么位置。
因为这是个实现问题,与程序员对程序设计语言的使用没有任何关系。
尤其,对于爱捣鼓函数虚表和虚表指针的童鞋,捣鼓之前要知道这些个事情不能用到工程中,只限于加强学习理解。
我们得出的结论只限于某些平台(处理器体系结构&操作系统&编译器)。
参考资料:
1. X86体系的函数调用约定
2. std::uintptr_t 与 std::size_t 的关系