C++对象内存结构及多态实现
文章分类:C++编程
又学习了一把C++内存结构,总是忘记,防止今后到处找,今天买到博客里来。如果你对C++或者java对象模型一点都不知道,估计你也不知道我在讲什么。最起码你得知道实例数据和类定义中的成员变量和成员函数在内存中的模型吧。
SUN的JVM的类内存结构倒是记得清楚,不知为什么,C++的倒是老搞不清,也许C++暴露的太多,很多地方要用,很容易搞混,Java的话,一般不会触及这么深。
言归正传,虚函数的内存结构是以类的第一个地址指向的一串函数列表,左边一串是类实例的内存结构,第一个内存里面存的就是虚函数表的地址。
图中b是一个类实例的内存结构,它的第一个地址指向了一串函数列表,这些就是虚函数表的地址。如果子类重写了某个虚函数,他的虚函数表就是:
可以看到,这个实例的虚函数表中,基类的::f()覆盖了基类的位置,这是子类的虚函数表。
还有一点很重要不管是父类还是子类,它们的虚函数表地址都是存在类实例的第一个地址中,并不是有两份,这也是多态的关键。
如果有以下代码,那么实际上发生了什么呢?
- Base *d = new Derive();
Base *d = new Derive();
实际上,我们按照Derive的定义分配了一块内存,但是将它赋给了Base类型使用,所以之后使用的时候都是按照Base的结构使用的,所以即使内存中有Derive特有的内存结构,但是不会被使用,因为Base类型并不知道。那虚函数怎么工作的呢?
实际上子类Derive和父类Base共同定义了虚函数表在类结构中的定义,那就是类的第一个地址的值,所以其实就算一父类的方式访问虚函数,但是指向的还是子类的虚函数表,而且前面说过,方法::f()已经被子类覆盖,这就让父类在调用虚函数::f()的时候,实际上是用的是子类的方法。
而如果不是虚函数,是一般的同名函数,那么父类的方法不会被覆盖,而是会多一个子类的方法,那么父类调用的时候就不会用到子类的方法。
这时方法::funcA()就不会覆盖原来的方法,所以下面的代码访问的是父类的方法。
- Base *d = new Derive();
- d->funcA();
Base *d = new Derive();
d->funcA();
下面的代码打印了父类和子类的虚函数表。
看下面这段代码,猜猜输出是什么?
- #include <cstdlib>
- #include <iostream>
- using namespace std;
- class Base{
- public:
- virtual void printA(){ cout << "Base::printA() " <<endl; }
- virtual void printB(){ cout << "Base::printB() " <<endl; }
- virtual void printC(){ cout << "Base::printC() " <<endl; }
- };
- class Derive: public Base{
- public:
- void printA(){ cout << "Derive::printA() " <<endl; }
- void printB(){ cout << "Derive::printB() " <<endl; }
- virtual void printD(){ cout << "Derive::printD() " <<endl; }
- };
- void printFuncLists(int funcAdd)
- {
- typedef void(* FUN)(void);
- int i = 0;
- while(*((int *)funcAdd+i) != 0)
- {
- FUN pFun = (FUN)*((int*)funcAdd+i);
- pFun();
- i++;
- }
- };
- void printVTB(int classAdd)
- {
- cout << "Class address : " << classAdd << endl;
- cout << "VTB start address : " << *(int*)classAdd << endl;
- printFuncLists(*(int*)classAdd);
- };
- int main(int argc, char *argv[])
- {
- cout << "Print Derive VFT as Base" << endl;
- Base *b = new Base();
- printVTB((int)&(*b));
- cout << "Print Derive VFT" << endl;
- Base *d = new Derive();
- printVTB((int)&(*d));
- system("PAUSE");
- return EXIT_SUCCESS;
- }
#include <cstdlib>
#include <iostream>
using namespace std;
class Base{
public:
virtual void printA(){ cout << "Base::printA() " <<endl; }
virtual void printB(){ cout << "Base::printB() " <<endl; }
virtual void printC(){ cout << "Base::printC() " <<endl; }
};
class Derive: public Base{
public:
void printA(){ cout << "Derive::printA() " <<endl; }
void printB(){ cout << "Derive::printB() " <<endl; }
virtual void printD(){ cout << "Derive::printD() " <<endl; }
};
void printFuncLists(int funcAdd)
{
typedef void(* FUN)(void);
int i = 0;
while(*((int *)funcAdd+i) != 0)
{
FUN pFun = (FUN)*((int*)funcAdd+i);
pFun();
i++;
}
};
void printVTB(int classAdd)
{
cout << "Class address : " << classAdd << endl;
cout << "VTB start address : " << *(int*)classAdd << endl;
printFuncLists(*(int*)classAdd);
};
int main(int argc, char *argv[])
{
cout << "Print Derive VFT as Base" << endl;
Base *b = new Base();
printVTB((int)&(*b));
cout << "Print Derive VFT" << endl;
Base *d = new Derive();
printVTB((int)&(*d));
system("PAUSE");
return EXIT_SUCCESS;
}
这段代码主要是用来打印虚函数表的,主要是看看虚函数怎么存储的,然后你可以验证一下子类的函数覆盖了父类的函数。
输出应该是:
- Print Derive VFT as Base
- Class address : 2887456
- VTB start address : 4463876
- Base::printA()
- Base::printB()
- Base::printC()
- Print Derive VFT
- Class address : 2887560
- VTB start address : 4463900
- Derive::printA()
- Derive::printB()
- Base::printC()
- Derive::printD()
Print Derive VFT as Base
Class address : 2887456
VTB start address : 4463876
Base::printA()
Base::printB()
Base::printC()
Print Derive VFT
Class address : 2887560
VTB start address : 4463900
Derive::printA()
Derive::printB()
Base::printC()
Derive::printD()
可以看到覆盖了,正是因为父类和子类的虚函数表地址一样(都是类的第一个地址),而且子类相同的虚函数覆盖了父类的函数地址,这才有了多态的功能。
参考了 http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html 的虚函数表结构,但是没有指明类实例访问过程。
听说《深度探索C++对象模型》 (Inside The C++ Object Model)不错,如果我没有说清楚,而你又想弄清楚的话可以去看看,java的话可以看《深入java虚拟机》,不过好像绝版了。