您的位置:首页 > 编程语言 > C语言/C++

C++虚函数表学习心得之由类实例地址到虚函数表再到虚函数地址中各种地址解析

2012-10-15 23:41 267 查看
C++虚函数是继承多态的核心,常规语法相信很多朋友都很熟悉了,这里有一篇牛人关于C++虚函数表很好的博客:http://blog.csdn.net/haoel/article/details/1948051/

我这里只是在此基础上谈谈各种地址与指针转换的心得。

大家都知道虚函数表在内存中存在的地址位于类实例首地址中,楼主这里的编程环境是WIn7 64bit,VS2010,废话少说直接看代码吧。

class Base{
public:
virtual void f(){
cout<<"base f()"<<endl;
}
virtual void g(){
cout<<"base g()"<<endl;
}
};
class Derived:public Base{
public:
void f(){
cout<<"derived f()"<<endl;
}
void g(){
cout<<"derived g()"<<endl;
}
};

这里有一个十分简单的继承关系:

下面就来看看具体各种地址的情况。

再次假设有一个Derived的实例d,下满看看这张图



这是一张与实例d的虚函数表有关的内存模型,途中方框中表示内存地址中具体的值,右边的横线表示所在的内存地址,下面我们就以d为例来进行填空哈。

首先填写d首地址的值及内容的值,很简单,相信学过指针的都会,d首地址是&d,d的首地址中内容是*(int*)&d,由于我机器(大部分机器)指针是4字节,所以要进行强制转换,将其转换成指针好方便获取其内容。



大家都知道存在虚函数的类实例首地址内容指向其对应的虚函数表,那么就要先将这个类实例首地址中的内容转换成指针,从而得到指向虚函数表的指针,那么我们就可以填写虚函数表那两个空了,虚函数表首地址:(int*)*(int*)&d,虚函数表首地址中内容的值:*(int*)*(int*)&d。

下面看看表中的函数,大家都知道,一维数组第一个元素的地址和数组首地址是一样的,那么简而易之,虚函数表中第一个虚函数的地址和虚函数表的地址一致,虚函数表中函数的顺序与具体类中虚函数声明的顺序一致,下面来填写虚函数表第一项的空白,虚函数第一表项(Derived::f())所在内存地址的值:(int*)*(int*)&d,虚函数第一表项(Derived::f())所在内存地址其内容的值:*(int*)*(int*)&d,其实这两个与虚函数的地址和地址中内容的值是一样的,其内容的值是函数具体的代码所在的内存的地址。



再来填写第二个表项,也就是虚函数Derived::g()的情况,由于两个函数相邻,只差一个指针的大小,也就是4字节,因此前面的函数f()的指针+1就可以得到g()的地址:

虚函数第二表项(Derived::g())所在内存地址的值:(int*)*(int*)&d+1,虚函数第二表项(Derived::g())所在内存地址其内容的值:*((int*)*(int*)&d+1)。



最后看看两个具体函数的情况,相信经过以上分析大家能很快填出他们的在内存中的地址(分别是虚函数表中表项的内容!):

虚函数Derived::f()虚函数入口地址的值:*(int*)*(int*)&d,虚函数Derived::g()虚函数入口地址的值:*((int*)*(int*)&d+1)。



下面我们就得到一张完整的虚函数表内存模型图:



下面进行测试代码编写,如下:

typedef void (*pFun)(void);
int _tmain(int argc, _TCHAR* argv[])
{
Derived d;
cout<<"d的首地址的值:"<<&d<<endl;
cout<<"d首地址内容:"<<*(int*)&d<<endl;
cout<<"虚函数表首地址的值、虚函数表第一表项所在内存地址的值:"<<(int*)*(int*)&d<<endl;
cout<<"虚函数表第一表项内容、Derived::f()函数首地址的值:"<<*(int*)*(int*)&d<<endl;
cout<<"虚函数表第二表项地址的值:"<<(int*)*(int*)&d+1<<endl;
cout<<"虚函数表第二表项内容、Derived::g()函数首地址的值:"<<*((int*)*(int*)&d+1)<<endl;
pFun p=(pFun)(*(int*)*(int*)&d);
p();
p=(pFun)(*((int*)*(int*)&d+1));
p();
int i;
cin>>i;
return 0;
}


运行结果:



那么到底是每个类在内存中只有一张虚函数表还是每个类的实例都保存一张虚函数表呢,测试一下吧:

typedef void (*pFun)(void);
int _tmain(int argc, _TCHAR* argv[])
{
Derived d;
cout<<"d的首地址的值:"<<&d<<endl;
cout<<"d首地址内容:"<<*(int*)&d<<endl;
cout<<"虚函数表首地址的值、虚函数表第一表项所在内存地址的值:"<<(int*)*(int*)&d<<endl;
cout<<"虚函数表第一表项内容、Derived::f()函数首地址的值:"<<*(int*)*(int*)&d<<endl;
cout<<"虚函数表第二表项地址的值:"<<(int*)*(int*)&d+1<<endl;
cout<<"虚函数表第二表项内容、Derived::g()函数首地址的值:"<<*((int*)*(int*)&d+1)<<endl;
Derived d2;
cout<<"d的首地址的值:"<<&d2<<endl;
cout<<"d首地址内容:"<<*(int*)&d2<<endl;
cout<<"虚函数表首地址的值、虚函数表第一表项所在内存地址的值:"<<(int*)*(int*)&d2<<endl;
cout<<"虚函数表第一表项内容、Derived::f()函数首地址的值:"<<*(int*)*(int*)&d2<<endl;
cout<<"虚函数表第二表项地址的值:"<<(int*)*(int*)&d2+1<<endl;
cout<<"虚函数表第二表项内容、Derived::g()函数首地址的值:"<<*((int*)*(int*)&d2+1)<<endl;
int i;
cin>>i;
return 0;
}

运行结果:



由此可见d和d1的虚函数表地址一样,两个虚函数函数f()和g()的函数入口地址也一样。

结论:每个存在虚函数的类在内存中有且只保存一张虚函数表。

PS:其实*(int*)&d和(int*)*(int*)&d其实打印的值是一样的,只是前者用10进制表示,后者用16进制表示的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: