C++虚函数表学习心得之由类实例地址到虚函数表再到虚函数地址中各种地址解析
2012-10-15 23:41
267 查看
C++虚函数是继承多态的核心,常规语法相信很多朋友都很熟悉了,这里有一篇牛人关于C++虚函数表很好的博客:http://blog.csdn.net/haoel/article/details/1948051/
我这里只是在此基础上谈谈各种地址与指针转换的心得。
大家都知道虚函数表在内存中存在的地址位于类实例首地址中,楼主这里的编程环境是WIn7 64bit,VS2010,废话少说直接看代码吧。
这里有一个十分简单的继承关系:
下面就来看看具体各种地址的情况。
再次假设有一个Derived的实例d,下满看看这张图
![](http://img.my.csdn.net/uploads/201210/15/1350311881_2666.png)
这是一张与实例d的虚函数表有关的内存模型,途中方框中表示内存地址中具体的值,右边的横线表示所在的内存地址,下面我们就以d为例来进行填空哈。
首先填写d首地址的值及内容的值,很简单,相信学过指针的都会,d首地址是&d,d的首地址中内容是*(int*)&d,由于我机器(大部分机器)指针是4字节,所以要进行强制转换,将其转换成指针好方便获取其内容。
![](http://img.my.csdn.net/uploads/201210/15/1350313489_9306.jpg)
大家都知道存在虚函数的类实例首地址内容指向其对应的虚函数表,那么就要先将这个类实例首地址中的内容转换成指针,从而得到指向虚函数表的指针,那么我们就可以填写虚函数表那两个空了,虚函数表首地址:(int*)*(int*)&d,虚函数表首地址中内容的值:*(int*)*(int*)&d。
下面看看表中的函数,大家都知道,一维数组第一个元素的地址和数组首地址是一样的,那么简而易之,虚函数表中第一个虚函数的地址和虚函数表的地址一致,虚函数表中函数的顺序与具体类中虚函数声明的顺序一致,下面来填写虚函数表第一项的空白,虚函数第一表项(Derived::f())所在内存地址的值:(int*)*(int*)&d,虚函数第一表项(Derived::f())所在内存地址其内容的值:*(int*)*(int*)&d,其实这两个与虚函数的地址和地址中内容的值是一样的,其内容的值是函数具体的代码所在的内存的地址。
![](http://img.my.csdn.net/uploads/201210/15/1350315394_7814.jpg)
再来填写第二个表项,也就是虚函数Derived::g()的情况,由于两个函数相邻,只差一个指针的大小,也就是4字节,因此前面的函数f()的指针+1就可以得到g()的地址:
虚函数第二表项(Derived::g())所在内存地址的值:(int*)*(int*)&d+1,虚函数第二表项(Derived::g())所在内存地址其内容的值:*((int*)*(int*)&d+1)。
![](http://img.my.csdn.net/uploads/201210/15/1350313751_7116.jpg)
最后看看两个具体函数的情况,相信经过以上分析大家能很快填出他们的在内存中的地址(分别是虚函数表中表项的内容!):
虚函数Derived::f()虚函数入口地址的值:*(int*)*(int*)&d,虚函数Derived::g()虚函数入口地址的值:*((int*)*(int*)&d+1)。
![](http://img.my.csdn.net/uploads/201210/15/1350313862_9480.jpg)
下面我们就得到一张完整的虚函数表内存模型图:
![](http://img.my.csdn.net/uploads/201210/15/1350315487_8800.png)
下面进行测试代码编写,如下:
运行结果:
![](http://img.my.csdn.net/uploads/201210/15/1350315675_7539.jpg)
那么到底是每个类在内存中只有一张虚函数表还是每个类的实例都保存一张虚函数表呢,测试一下吧:
运行结果:
![](http://img.my.csdn.net/uploads/201210/15/1350315837_1505.jpg)
由此可见d和d1的虚函数表地址一样,两个虚函数函数f()和g()的函数入口地址也一样。
结论:每个存在虚函数的类在内存中有且只保存一张虚函数表。
PS:其实*(int*)&d和(int*)*(int*)&d其实打印的值是一样的,只是前者用10进制表示,后者用16进制表示的。
我这里只是在此基础上谈谈各种地址与指针转换的心得。
大家都知道虚函数表在内存中存在的地址位于类实例首地址中,楼主这里的编程环境是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,下满看看这张图
![](http://img.my.csdn.net/uploads/201210/15/1350311881_2666.png)
这是一张与实例d的虚函数表有关的内存模型,途中方框中表示内存地址中具体的值,右边的横线表示所在的内存地址,下面我们就以d为例来进行填空哈。
首先填写d首地址的值及内容的值,很简单,相信学过指针的都会,d首地址是&d,d的首地址中内容是*(int*)&d,由于我机器(大部分机器)指针是4字节,所以要进行强制转换,将其转换成指针好方便获取其内容。
![](http://img.my.csdn.net/uploads/201210/15/1350313489_9306.jpg)
大家都知道存在虚函数的类实例首地址内容指向其对应的虚函数表,那么就要先将这个类实例首地址中的内容转换成指针,从而得到指向虚函数表的指针,那么我们就可以填写虚函数表那两个空了,虚函数表首地址:(int*)*(int*)&d,虚函数表首地址中内容的值:*(int*)*(int*)&d。
下面看看表中的函数,大家都知道,一维数组第一个元素的地址和数组首地址是一样的,那么简而易之,虚函数表中第一个虚函数的地址和虚函数表的地址一致,虚函数表中函数的顺序与具体类中虚函数声明的顺序一致,下面来填写虚函数表第一项的空白,虚函数第一表项(Derived::f())所在内存地址的值:(int*)*(int*)&d,虚函数第一表项(Derived::f())所在内存地址其内容的值:*(int*)*(int*)&d,其实这两个与虚函数的地址和地址中内容的值是一样的,其内容的值是函数具体的代码所在的内存的地址。
![](http://img.my.csdn.net/uploads/201210/15/1350315394_7814.jpg)
再来填写第二个表项,也就是虚函数Derived::g()的情况,由于两个函数相邻,只差一个指针的大小,也就是4字节,因此前面的函数f()的指针+1就可以得到g()的地址:
虚函数第二表项(Derived::g())所在内存地址的值:(int*)*(int*)&d+1,虚函数第二表项(Derived::g())所在内存地址其内容的值:*((int*)*(int*)&d+1)。
![](http://img.my.csdn.net/uploads/201210/15/1350313751_7116.jpg)
最后看看两个具体函数的情况,相信经过以上分析大家能很快填出他们的在内存中的地址(分别是虚函数表中表项的内容!):
虚函数Derived::f()虚函数入口地址的值:*(int*)*(int*)&d,虚函数Derived::g()虚函数入口地址的值:*((int*)*(int*)&d+1)。
![](http://img.my.csdn.net/uploads/201210/15/1350313862_9480.jpg)
下面我们就得到一张完整的虚函数表内存模型图:
![](http://img.my.csdn.net/uploads/201210/15/1350315487_8800.png)
下面进行测试代码编写,如下:
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; }
运行结果:
![](http://img.my.csdn.net/uploads/201210/15/1350315675_7539.jpg)
那么到底是每个类在内存中只有一张虚函数表还是每个类的实例都保存一张虚函数表呢,测试一下吧:
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; }
运行结果:
![](http://img.my.csdn.net/uploads/201210/15/1350315837_1505.jpg)
由此可见d和d1的虚函数表地址一样,两个虚函数函数f()和g()的函数入口地址也一样。
结论:每个存在虚函数的类在内存中有且只保存一张虚函数表。
PS:其实*(int*)&d和(int*)*(int*)&d其实打印的值是一样的,只是前者用10进制表示,后者用16进制表示的。
相关文章推荐
- SEO学习笔记之实例解析Robots和各种蜘蛛
- Linux下的地址解析函数应用实例
- C++ 虚函数表、函数地址、内存布局解析
- Android学习心得(16) --- Dex文件结构实例解析(2)
- 【python学习资料】各种python 函数参数定义和解析 --转载自byte_way
- UNIX环境编程学习笔记------编程实例----域名解析函数的函数原型即getaddrinfo()函数原型
- Linux下的地址解析函数(res_*)的应用实例
- C++ 虚函数表解析(比较清楚,还可打印虚函数地址)
- 每日学习心得:SQL查询表的行列转换/小计/统计(with rollup,with cube,pivot解析)
- linux驱动学习心得--以I2C做实例
- php学习笔记(二十)header()函数常用实例
- Java反射学习总结四(动态代理使用实例和内部原理解析)
- 初学者学习分布式的一点心得,关于Remoting的分布式简单实例的
- 虚函数表所指向的虚函数地址数组存放在哪里?
- 从零开始学习 ASP.NET MVC 1.0 (五) ViewEngine 深入解析与应用实例
- 用 Addr2line 将函数地址解析为函数名
- LINQ学习心得分享--------(四)LINQ TO XML实用解析
- 改写c++自带sort排序函数实例解析
- JXCELL实例学习与研究(二) 之 EXCEL中各种图表格式的显示 超链接(链接到另外的单元格、WEBSITE、E-MAIL、本地磁盘/文件)