或许编译器的差异, 所得的结果也不同, 但某些地方还是可以借鉴的.
帖子讨论:
http://topic.csdn.net/u/20110509/11/43917452-ae4d-4ba9-8c72-25e454e60e75.html?113761501
参考:
http://www.kuqin.com/language/20090314/39911.html
http://www.cse.wustl.edu/~mdeters/seminar/fall2005/mi.html
class CPocket { public: CPocket(){ //cout << "CPocket()" << endl; ptr = NULL; val = 0; }; //virtual ~CPocket(){}; //virtual ~CPocket(){}; virtual void func1(); virtual void func2(); virtual void func3();// protected: char* ptr; private: int val; }; class DCPocket:public CPocket { int a; public: DCPocket() { a =15;}; //virtual ~DCPocket() {}; virtual void func2(); // }; class D2CPocket:public DCPocket { int b; public: virtual void func1(); // virtual void func4(); // ~D2CPocket(){}; }; typedef void (*pFunc)(void); int main(int argc, char* argv[]) { pFunc pf = NULL; //CPocket b; //DCPocket d; D2CPocket d2; //func1~func4 int* p = (int*)*((int*)(&d2)); // p == v_table[] /* pf = (pFunc)(*p); pf(); pf = (pFunc)(*(p+1)); pf(); pf = (pFunc)(*(p+2)); pf(); pf = (pFunc)(*(p+3)); pf(); */ std::cout << "..." << std::endl; system("PAUSE"); return EXIT_SUCCESS; }
运行结果如下:
run D2CPocket::func1()
run DCPocket::func2()
run CPocket::func3()
run D2CPocket::func4()
在VC++ 6.0 和CodeBlock中用watch查看D2CPocket 的实例d2所占的内存, 大致存储顺序如下:
|--CPocket成员所占的内存
|--DCPocket成员所占的内存
|--D2CPocket成员所占的内存
即是这样的:
CPocket虚函数表
CPocket::ptr
CPocket::val
DCPocket:seq
D2CPocket::index
结论1:类实例所占内存结构是基类成员在前,派生类成员在后的顺序.
结论2:类实例中的开头4字节, 是一个指针(即虚函数表指针), 指向一个数组vtable[], 数组的每个元素是虚函数地址.
所以, (类实例开始的四字节)虚函数指针用解操作2次, 才能得到虚函数的入口地址;
还发现了一个小问题, 代码中的三个类CPocket, DCPocket, D2CPocket都是没有"虚析构函数", 如果类CPocket, DCPocket加上虚析构,再次执行代码, 会发现一些有意思的小现象, 不同编译器表现不同, 大致是虚函数被"意外"的调用了,
为什么呢? 用vc6的watch查看类实例d2的虚函数表, 发现~ vtable[0]的内容, 是虚析构函数的地址,,,
所以第一次调用
pf = (pFunc)(*p);
pf(); 的时候,调用的是派生类的虚析构函数,
结论3:如果派生类的基类中还有虚析构, 那么派生类实例的虚函数表开头是该类的析构函数.
*注,如果某类不会当做基类, 那么不必把该类的虚构声明为virtual.