1.(mov ecx,dword ptr [ebp-0Ch])将this指针压入ecx
2.(mov edx,dword ptr [ecx])this指针指向该对象的首地址,而该处的前四个字节存放着该对象的虚函数表的首地址,将虚表指针放到edx中。
3.(call dword ptr [edx+4])由于edx中存放着虚表指针,则edx+4表示调用该虚表中的第二个函数
4.执行到上述操作后,执行该条指令(jmp B::say (00401320)),从而真正调用我们的虚函数!
如果我们的程序是通过指向对象的指针或者是引用来调用该对象的虚函数,则在调用虚函数的过程需要查表(虚函数表)来调用真正的函数。
调用的不是虚函数则不需要查表,在编译时即可确定调用的是那个函数。
如果是通过对象来调用则对任何类型的函数都不需要查表。
虚函数指针是在对象的构造函数中初始化的。
关于虚表指针的测试程序:
测试一:
1 #include <iostream> 2 using namespace std; 3 class A 4 { 5 private: 6 int a_val; 7 public: 8 virtual void show(){cout<<"show"<<a_val<<endl;} 9 virtual void say(){cout<<"say"<<endl;} 10 inline void setVal(int val){a_val = val;} 11 }; 12 int main() 13 { 14 A a; 15 a.setVal(123); 16 17 //受保护的虚函数是不能被直接调用的 18 //而通过虚表指针的地址拷贝则可实现函数的调运 19 //a.show(); 20 21 //该指针存放虚表地址 22 int *des = new int; 23 24 //该指针存放虚表中的第一个函数 25 int *ptr = new int; 26 memcpy(des,&a,4); 27 memcpy(ptr,reinterpret_cast<int *>(*des),4); 28 void (*pshow)() = reinterpret_cast<void (*)()>(*ptr); 29 30 //依据__thiscall的调用约定将this指针传入ecx 31 //从而使虚函数能够正确地取出参数 32 int addre = reinterpret_cast<int>(&a); 33 __asm 34 { 35 mov ecx,addre 36 }; 37 38 pshow(); 39 40 //获得私有的成员v_val 41 memcpy(des, reinterpret_cast<int *>(&a)+1, 4); 42 cout<<*des<<endl; 43 44 return 0; 45 }
测试二:
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 class A 5 { 6 private: 7 int a_val; 8 public: 9 virtual void show(std::string str) 10 { 11 /* 12 int addr = reinterpret_cast<int>(this); 13 __asm 14 { 15 mov ecx,addr 16 } 17 */ 18 cout<<a_val<<endl; 19 cout<<str<<endl; 20 } 21 virtual void say(){cout<<"say"<<endl;} 22 inline void setVal(int val){a_val = val;} 23 }; 24 /* 25 class B: public A 26 { 27 private: 28 int b_val; 29 public: 30 void say(){cout<<"B in say"<<endl;} 31 virtual void hello(){cout<<"hello"<<endl;} 32 }; 33 */ 34 int main() 35 { 36 A a; 37 a.setVal(123); 38 //受保护的虚函数是不能被直接调用的 39 //而通过虚表指针的地址拷贝则可实现函数的调运 40 //a.show(); 41 42 //该指针存放虚表地址 43 int *des = new int; 44 45 //该指针存放虚表中的第一个函数 46 int *ptr = new int; 47 48 memcpy(des,&a,4); 49 memcpy(ptr,reinterpret_cast<int *>(*des),4); 50 51 void (*pshow)(std::string) = reinterpret_cast<void (*)(std::string)>(*ptr); 52 53 int addre = reinterpret_cast<int>(&a); 54 __asm 55 { 56 mov ecx,addre 57 }; 58 59 string str("hello world"); 60 pshow(str); 61 62 __asm 63 { 64 sub esp,10h 65 }; 66 67 //获得私有的成员v_val 68 memcpy(des, reinterpret_cast<int *>(&a)+1, 4); 69 cout<<*des<<endl; 70 71 //cout<<*des<<endl; 72 //cout<<*ptr<<endl; 73 74 return 0; 75 }
测试三:
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 class A 5 { 6 private: 7 char a_val; 8 public: 9 virtual void show() 10 { 11 cout<<"show"<<endl; 12 } 13 14 virtual void say(){cout<<"say"<<endl;} 15 inline void seta_Val(char val){a_val = val;} 16 inline char geta_Val()const{return a_val;} 17 }; 18 class B: public A 19 { 20 private: 21 int b_val; 22 public: 23 void say(){cout<<"B in say"<<endl;} 24 virtual void hello(){cout<<"hello"<<endl;} 25 inline void setb_Val(int val){b_val = val;} 26 inline int getb_Val()const{return b_val;} 27 }; 28 int main() 29 { 30 B b; 31 b.setb_Val(123); 32 b.seta_Val('A'); 33 34 int *vfptr = new int; 35 int *pf = new int; 36 37 memcpy(vfptr, &b, 4); 38 memcpy(pf, reinterpret_cast<int *>(*vfptr)+2, 4); 39 40 void (*pfun)() = reinterpret_cast<void (*)()>(*pf); 41 pfun(); 42 43 char *pa_val = new char; 44 int *pb_val = new int; 45 memcpy(pa_val, reinterpret_cast<int *>(&b)+1, sizeof(char)); 46 memcpy(pb_val, reinterpret_cast<int *>(&b)+2, sizeof(int)); 47 48 cout<<*pa_val<<endl; 49 cout<<*pb_val<<endl; 50 cout<<"<<<<<<<<<<<<<<"<<endl; 51 52 *pa_val = 'B'; 53 *pb_val = 999; 54 55 memcpy(reinterpret_cast<int *>(&b)+1, pa_val, sizeof(char)); 56 memcpy(reinterpret_cast<int *>(&b)+2, pb_val, 4); 57 58 cout<<b.geta_Val()<<endl; 59 cout<<b.getb_Val()<<endl; 60 return 0; 61 }
由以上测试程序可以得出以下结论:
1.c++对象(基类对象)的内存布局是:对象的内存地址(&a)所指向的内存中的前四个字节中存放的是该对象的虚函数表的首地址(前提是该对象有虚函数),接下来的内存中依次存放该对象的数据成员(非静态的数据成员)。
注意:对象的虚函数表中存放的实际上并不是虚函数的入口地址,而是一个跳转指令(jmp)的地址,该跳转指令,转向虚函数的入口,为了叙述方便,我这里作出约定:我们就认为虚函数表中就存放的是虚函数的入口地址。
虚函数的存放顺序与函数的声明顺序是相同的。
2.派生类的对象的内存布局是:前四个字节依然存放虚表指针,虚表中首先存放父类的虚函数地址,注意,由于派生类中也可能有①自己的虚函数,同时派生类也可以②重写父类的虚函数,虚函数表的分布如何:
对于情况一而言,将派生类新增加的虚函数地址依次添加到虚表(虚表中已经有父类的虚函数地址)的后面。
对于情况二而言,如果派生类重写了父类的虚函数,则将重写后的虚函数地址替换掉父类原来的虚函数地址,如果没有重写,则按照父类的虚表顺序存放虚函数地址
接下来的内存中依次存放该对象的父类的数据成员(非静态的数据成员),然后再存放派生类自己的数据成员。(还有内存对齐的问题)
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 class A 5 { 6 private: 7 char a_val; 8 int a_val2; 9 public: 10 virtual void show(){cout<<"show"<<endl;} 11 virtual void say(){cout<<"say"<<endl;} 12 13 inline void seta_Val(char val){a_val = val;} 14 inline void seta_Val2(int val2){a_val2 = val2;} 15 16 inline char geta_Val()const{return a_val;} 17 inline int geta_Val2()const{return a_val2;} 18 19 }; 20 class B: public A 21 { 22 private: 23 int b_val; 24 char b_val2; 25 public: 26 void say(){cout<<"B in say"<<endl;} 27 virtual void hello(){cout<<"hello"<<endl;} 28 29 inline void setb_Val(int val){b_val = val;} 30 inline void setb_Val2(char val2){b_val2 = val2;} 31 32 inline int getb_Val()const{return b_val;} 33 inline char getb_Val2()const{return b_val2;} 34 }; 35 int main() 36 { 37 B b; 38 b.seta_Val('A'); 39 b.seta_Val2(1); 40 b.setb_Val(2); 41 b.setb_Val2('B'); 42 43 int *vfptr = new int; 44 int *pf = new int; 45 46 memcpy(vfptr, &b, 4); 47 memcpy(pf, reinterpret_cast<int *>(*vfptr)+2, 4); 48 49 void (*pfun)() = reinterpret_cast<void (*)()>(*pf); 50 pfun(); 51 52 char *pa_val = new char; 53 int *pb_val = new int; 54 55 memcpy(pa_val, reinterpret_cast<int *>(&b)+1, sizeof(char)); 56 memcpy(pb_val, reinterpret_cast<int *>(&b)+2, sizeof(int)); 57 58 cout<<*pa_val<<endl; 59 cout<<*pb_val<<endl; 60 61 memcpy(pb_val, reinterpret_cast<int *>(&b)+3, sizeof(int)); 62 //存在内存对齐的问题 63 memcpy(pa_val, reinterpret_cast<int *>(&b)+4, sizeof(char)); 64 65 cout<<*pb_val<<endl; 66 cout<<*pa_val<<endl; 67 68 /* 69 cout<<"<<<<<<<<<<<<<<"<<endl; 70 71 *pa_val = 'B'; 72 *pb_val = 999; 73 74 memcpy(reinterpret_cast<int *>(&b)+1, pa_val, sizeof(char)); 75 memcpy(reinterpret_cast<int *>(&b)+2, pb_val, 4); 76 77 cout<<b.geta_Val()<<endl; 78 cout<<b.getb_Val()<<endl; 79 */ 80 return 0; 81 }