C++虚函数表深入解析 (一)
来源:互联网 发布:acl 源 mac 目的ip 编辑:程序博客网 时间:2024/06/08 17:01
本篇文章大概从三个角度解析虚函数表 :
A : 虚函数调用方式
B : 深入解析虚函数
C : 打印虚函数表
有问题一起交流 !
A : 虚函数调用方式
关于函数调用方式,在此指的是直接调用与间接调用 , 即Call rel16/32 ( 其opcode E8 ... )或者 call [ rel16/32 ] ( 其opcode FF ...) .
具体call指令请参考: http://blog.ftofficer.com/2010/04/n-forms-of-call-instructions/
注 : 此处只涉及到近调用
测试代码 :
#include "stdafx.h"class CBase{public:int x ;int y ;virtual void Fun1(){printf("Fun1\n");}};int _tmain(int argc, _TCHAR* argv[]){CBase base1;CBase* pb = &base1 ;//利用对象直接调用成员函数base1.Fun1();//利用类的指针调用成员函数pb->Fun1();return 0;}
代码中中含有一个类CBase , 在main函数中定义了该类的对象和指针 ; 然后分别用两种方式调用 :
重要的在反汇编代码 :
19: int _tmain(int argc, _TCHAR* argv[]) 20: {000314E0 55 push ebp 000314E1 8B EC mov ebp,esp 000314E3 81 EC E4 00 00 00 sub esp,0E4h 000314E9 53 push ebx 000314EA 56 push esi 000314EB 57 push edi 000314EC 8D BD 1C FF FF FF lea edi,[ebp+FFFFFF1Ch] 000314F2 B9 39 00 00 00 mov ecx,39h 000314F7 B8 CC CC CC CC mov eax,0CCCCCCCCh 000314FC F3 AB rep stos dword ptr es:[edi] 000314FE A1 34 80 03 00 mov eax,dword ptr ds:[00038034h] 00031503 33 C5 xor eax,ebp 00031505 89 45 FC mov dword ptr [ebp-4],eax 21: CBase base1;00031508 8D 4D EC lea ecx,[ebp-14h] /* 参数为this指针 */0003150B E8 58 FC FF FF call 00031168 /* 调用构造函数 */ 22: CBase* pb = &base1 ;00031510 8D 45 EC lea eax,[ebp-14h] /* 为新创建的类指针分配空间并赋值为该对象this指针(即该对象首个成员的地址) */</span>00031513 89 45 E0 mov dword ptr [ebp-20h],eax 23: //利用对象直接调用成员函数</span> 24: base1.Fun1();00031516 8D 4D EC lea ecx,[ebp-14h] /* 参数为this指针 */</span>00031519 E8 90 FC FF FF call 000311AE 25: //利用类的指针调用成员函数</span> 26: pb->Fun1();0003151E 8B 45 E0 mov eax,dword ptr [ebp-20h] /* [ebp-20h]存储的是该对象的this指针 */00031521 8B 10 mov edx,dword ptr [eax] /* 将该对象的首个成员存储的EDX */00031523 8B F4 mov esi,esp /* 检查堆栈平衡时用的 ,不必深究 */00031525 8B 4D E0 mov ecx,dword ptr [ebp-20h] /* 参数为this指针 */00031528 8B 02 mov eax,dword ptr [edx] /************** 将edx的值作为地址,取四个字节放到eax **************/0003152A FF D0 call eax 0003152C 3B F4 cmp esi,esp /* 检查堆栈平衡时用的 ,不必深究 */0003152E E8 21 FC FF FF call 00031154 27: return 0;00031533 33 C0 xor eax,eax 28: }
通过以上代码分析可得出结论 :
通过 对象 . 成员函数 的方式调用虚函数时使用的是直接调用方式(call rel32)
通过 指针->函数名 的方式调用虚函数时使用的是间接调用方式(call [rel32])
mov eax,dword ptr [edx] 这条指令中eax到底存放的是什么呢 ? 现在给出答案 :虚函数表的第一个虚函数 . 详细分析看第二模块
为了方便理解反汇编代码 , 在此附上显示符号的反汇编代码 :
19: int _tmain(int argc, _TCHAR* argv[]) 20: {000314E0 55 push ebp 000314E1 8B EC mov ebp,esp 000314E3 81 EC E4 00 00 00 sub esp,0E4h 000314E9 53 push ebx 000314EA 56 push esi 000314EB 57 push edi 000314EC 8D BD 1C FF FF FF lea edi,[ebp-0E4h] 000314F2 B9 39 00 00 00 mov ecx,39h 000314F7 B8 CC CC CC CC mov eax,0CCCCCCCCh 000314FC F3 AB rep stos dword ptr es:[edi] 000314FE A1 34 80 03 00 mov eax,dword ptr ds:[00038034h] 00031503 33 C5 xor eax,ebp 00031505 89 45 FC mov dword ptr [ebp-4],eax 21: CBase base1;00031508 8D 4D EC lea ecx,[base1] 0003150B E8 58 FC FF FF call CBase::CBase (031168h) 22: CBase* pb = &base1 ;00031510 8D 45 EC lea eax,[base1] 00031513 89 45 E0 mov dword ptr [pb],eax 23: //利用对象直接调用成员函数 24: base1.Fun1();00031516 8D 4D EC lea ecx,[base1] 00031519 E8 90 FC FF FF call CBase::Fun1 (0311AEh) 25: //利用类的指针调用成员函数 26: pb->Fun1();0003151E 8B 45 E0 mov eax,dword ptr [pb] 00031521 8B 10 mov edx,dword ptr [eax] 00031523 8B F4 mov esi,esp 00031525 8B 4D E0 mov ecx,dword ptr [pb] 00031528 8B 02 mov eax,dword ptr [edx] 0003152A FF D0 call eax 0003152C 3B F4 cmp esi,esp 0003152E E8 21 FC FF FF call __RTC_CheckEsp (031154h) 27: return 0;00031533 33 C0 xor eax,eax 28: }
B : 深入解析虚函数表
此模块我们主要探究什么是虚函数表 , 虚函数表的位置 .
测试代码1 :
#include "stdafx.h"class CBase{public://构造函数CBase(){x = 1;y = 2;}int x ;int y ;};int _tmain(int argc, _TCHAR* argv[]){CBase base1 ;printf("%d\n",sizeof(CBase));return 0;}
在类中没有定义虚函数 , 只有两个成员变量
输出结果 : 8
#include "stdafx.h"class CBase{public:CBase(){x = 1;y = 2;}int x ;int y ;virtual void Fun1(){printf("Fun1");}};int _tmain(int argc, _TCHAR* argv[]){CBase base1 ;printf("%d\n",sizeof(CBase));return 0;}
在类中定义了一个虚函数 , 两个成员变量 输出结果 : 12
多出了四个字节 , 如果我们定义两个虚函数呢 ? 该类的大小是多少呢 ?
#include "stdafx.h"class CBase{public:CBase(){x = 1;y = 2;}int x ;int y ;virtual void Fun1(){printf("Fun1");}virtual void Fun2(){printf("Fun2");}};int _tmain(int argc, _TCHAR* argv[]){CBase base1 ;printf("%d\n",sizeof(CBase));return 0;}
在类中定义了一个虚函数 , 两个成员变量 输出结果 : 12 仍然是12个字节
可以继续增加虚函数的个数 , 可以发现该类的大小仍然是12个字节 , 由此可以引发两个问题 :
1. 多出来的四个字节是存储什么的 ?
2. 为什么虚函数的个数增加时 , 类的大小不再发生变化 ?
如何解决这两个问题呢 ? 对于第一个问题简单 , 看下内存不就知道了
看到对象base1的空间内,第一个成员不知道什么东东(0x0133585c) , 第二个第三个成员分别是 x , y ;后面cccccccc就不是了, 只有12个字节大小不是么 ?
我们接着看第二幅图 :
很明显 : 0x0133585c这个地址中存放的是两个很规律的 "数" .
我们不难推测 : 这两个" 数"应该是两个虚函数的地址 .
上面这句话 mov eax,dword ptr [edx] 这条指令中eax到底存放的是什么呢 ? 现在给出答案 :虚函数表的第一个虚函数 . 详细分析看第二模块
现在应该有答案了吧 .
神马?? 还不清楚? 好吧 , 那我们就手动调用这些虚函数来印证一下. 搞起
测试代码 :
#include "stdafx.h"class CBase{public://构造函数CBase(){x = 1;y = 2;}int x ;int y ;virtual void Fun1(){printf("Fun1\n");}virtual void Fun2(){printf("Fun2\n");}virtual void Fun3(){printf("Fun3\n");}};int _tmain(int argc, _TCHAR* argv[]){CBase base1 ;//查看base1的虚函数表//&base1即this指针,第一个成员即虚函数表的位置printf("0x%x\n",&base1);//定义函数指针typedef void(*pFunction)(void);//循环调用三个虚函数for (int i = 0;i<3;i++){int ptemp = *((int*)*(int*)&base1+i);pFunction pFun = (pFunction)ptemp;pFun();}return 0;}
结果如图 :
好的,至此我们可以总结出 :
当有虚函数存在时 , 类的大小增加4个字节 , 这四个字节在该类对象的首四个字节 . 这四个字节存储的即是虚函数表的地址 ;
当虚函数个数增加时,类的大小不再增加,增加的是虚函数表中的函数地址 .
提出一个问题 :为什么虚函数采用的是间接调用方式?
虚函数本就是为继承而生的,失去了继承,利用虚函数实现多态将毫无意义.提出的问题会在后续连载中解答.
本文有些啰嗦 , 只是希望路过的朋友能够有一点点的收获 ...
- C++虚函数表深入解析 (一)
- 深入解析虚函数
- Function深入解析一
- 【c++】大牛解析虚函数表
- C++ 虚函数经典深入解析
- c++中基类虚函数表的深入解析
- js 函数深入解析
- 深入解析extern "C"
- 深入C#(一)
- 深入C#(一)
- Sql注入深入解析一
- 深入解析用户界面(一)
- Handler深入解析(一)
- Python range函数深入解析
- inline 函数的深入解析
- jQuery.on() 函数深入解析
- 深入解析C语言声明
- 深入解析C语言声明
- C++ 语言基础(1)
- 单链表逆置
- Google编码规范
- [VC] 设置系统时间
- 程序内新建模式对话框的句柄查找问题
- C++虚函数表深入解析 (一)
- ajax get方式请求
- 经典算法研究系列:二、Dijkstra 算法初探
- R语言之关联规则挖掘
- Wall(凸包POJ 1113)
- android button 的clickable 和 enable属性的个人见解
- 机器学习中的范数规则化:L0、L1与L2范数,核范数和规则项参数选择
- 快速排序
- 保利威视后台设置播放域名黑白名单