C++类实例占用的内存结构,及虚函数表的一点探究

或许编译器的差异, 所得的结果也不同, 但某些地方还是可以借鉴的.

帖子讨论:

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.

你可能感兴趣的:(C++类实例占用的内存结构,及虚函数表的一点探究)