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)),從而真正調用我們的虛函數!
如果我們的程序是通過指向對象的指針或者是引用來調用該對象的虛函數,則在調用虛函數的過程需要查表(虛函數表)來調用真正的函數。
調用的不是虛函數則不需要查表,在編譯時即可確定調用的是那個函數。
如果是通過對象來調用則對任何類型的函數都不需要查表。
虛函數指針是在對象的構造函數中初始化的。
關於虛表指針的測試程序:
//virtual1.cpp
#include <iostream>
using namespace std;
class A
{
private:
int a_val;
public:
virtual void show(){cout<<"show"<<a_val<<endl;}
virtual void say(){cout<<"say"<<endl;}
inline void setVal(int val){a_val = val;}
};
int main()
{
A a;
a.setVal(123);
//受保護的虛函數是不能被直接調用的
//而通過虛表指針的地址拷貝則可實現函數的調運
//a.show();
//該指針存放虛表地址
int *des = new int;
//該指針存放虛表中的第一個函數
int *ptr = new int;
memcpy(des,&a,4);
memcpy(ptr,reinterpret_cast<int *>(*des),4);
void (*pshow)() = reinterpret_cast<void (*)()>(*ptr);
//依據__thiscall的調用約定將this指針傳入ecx
//從而使虛函數能夠正確地取出參數
int addre = reinterpret_cast<int>(&a);
__asm34 {
mov ecx,addre
};
pshow();
//獲得私有的成員v_val
memcpy(des, reinterpret_cast<int *>(&a)+1, 4);
cout<<*des<<endl;
return 0;
}
//virtual2.cpp
#include <iostream>
#include <string>
using namespace std;
class A
{
private:
int a_val;
public:
virtual void show(std::string str)
{
/*
int addr = reinterpret_cast<int>(this);
__asm14 {
mov ecx,addr16 }
*/
cout<<a_val<<endl;
cout<<str<<endl;
}
virtual void say(){cout<<"say"<<endl;}
inline void setVal(int val){a_val = val;}
};
/*
class B: public A
{
private:
int b_val;
public:
void say(){cout<<"B in say"<<endl;}
virtual void hello(){cout<<"hello"<<endl;}
};
*/
int main()
{
A a;
a.setVal(123);
//受保護的虛函數是不能被直接調用的
//而通過虛表指針的地址拷貝則可實現函數的調運
//a.show();
//該指針存放虛表地址
int *des = new int;
//該指針存放虛表中的第一個函數
int *ptr = new int;
memcpy(des,&a,4);
memcpy(ptr,reinterpret_cast<int *>(*des),4);
void (*pshow)(std::string) = reinterpret_cast<void (*)(std::string)>(*ptr);
int addre = reinterpret_cast<int>(&a);
__asm55 {
mov ecx,addre57 };
string str("hello world");
pshow(str);
__asm63 {
sub esp,10h65 };
//獲得私有的成員v_val
memcpy(des, reinterpret_cast<int *>(&a)+1, 4);
cout<<*des<<endl;
//cout<<*des<<endl;
//cout<<*ptr<<endl;
return 0;
}
//virtual3.cpp
#include <iostream>
#include <string>
using namespace std;
class A
{
private:
char a_val;
public:
virtual void show()
{
cout<<"show"<<endl;
}
virtual void say(){cout<<"say"<<endl;}
inline void seta_Val(char val){a_val = val;}
inline char geta_Val()const{return a_val;}
};
class B: public A19 {
private:
int b_val;
public:
void say(){cout<<"B in say"<<endl;}
virtual void hello(){cout<<"hello"<<endl;}
inline void setb_Val(int val){b_val = val;}
inline int getb_Val()const{return b_val;}
};
int main()
{
B b;
b.setb_Val(123);
b.seta_Val('A');
int *vfptr = new int;
int *pf = new int;
memcpy(vfptr, &b, 4);
memcpy(pf, reinterpret_cast<int *>(*vfptr)+2, 4);
void (*pfun)() = reinterpret_cast<void (*)()>(*pf);
pfun();
char *pa_val = new char;
int *pb_val = new int;
memcpy(pa_val, reinterpret_cast<int *>(&b)+1, sizeof(char));
memcpy(pb_val, reinterpret_cast<int *>(&b)+2, sizeof(int));
cout<<*pa_val<<endl;
cout<<*pb_val<<endl;
cout<<"<<<<<<<<<<<<<<"<<endl;
*pa_val = 'B';
*pb_val = 999;
memcpy(reinterpret_cast<int *>(&b)+1, pa_val, sizeof(char));
memcpy(reinterpret_cast<int *>(&b)+2, pb_val, 4);
cout<<b.geta_Val()<<endl;
cout<<b.getb_Val()<<endl;
return 0;
}
由以上測試程序可以得出以下結論:
1.c++對象(基類對象)的內存佈局是:對象的內存地址(&a)所指向的內存中的前四個字節中存放的是該對象的虛函數表的首地址(前提是該對象有虛函數),接下來的內存中依次存放該對象的數據成員(非靜態的數據成員)。
注意:對象的虛函數表中存放的實際上並不是虛函數的入口地址,而是一個跳轉指令(jmp)的地址,該跳轉指令,轉向虛函數的入口,爲了敘述方便,我這裏作出約定:我們就認爲虛函數表中就存放的是虛函數的入口地址。
虛函數的存放順序與函數的聲明順序是相同的。
2.派生類的對象的內存佈局是:前四個字節依然存放虛表指針,虛表中首先存放父類的虛函數地址,注意,由於派生類中也可能有①自己的虛函數,同時派生類也可以②重寫父類的虛函數,虛函數表的分佈如何:
對於情況一而言,將派生類新增加的虛函數地址依次添加到虛表(虛表中已經有父類的虛函數地址)的後面。
對於情況二而言,如果派生類重寫了父類的虛函數,則將重寫後的虛函數地址替換掉父類原來的虛函數地址,如果沒有重寫,則按照父類的虛表順序存放虛函數地址
接下來的內存中依次存放該對象的父類的數據成員(非靜態的數據成員),然後再存放派生類自己的數據成員。(還有內存對齊的問題)
#include <iostream>
#include <string>
using namespace std;
class A
{
private:
char a_val;
int a_val2;
public:
virtual void show(){cout<<"show"<<endl;}
virtual void say(){cout<<"say"<<endl;}
inline void seta_Val(char val){a_val = val;}
inline void seta_Val2(int val2){a_val2 = val2;}
inline char geta_Val()const{return a_val;}
inline int geta_Val2()const{return a_val2;}
};
class B: public A21 {
private:
int b_val;
char b_val2;
public:
void say(){cout<<"B in say"<<endl;}
virtual void hello(){cout<<"hello"<<endl;}
inline void setb_Val(int val){b_val = val;}
inline void setb_Val2(char val2){b_val2 = val2;}
inline int getb_Val()const{return b_val;}
inline char getb_Val2()const{return b_val2;}
};
int main()
{
B b;
b.seta_Val('A');
b.seta_Val2(1);
b.setb_Val(2);
b.setb_Val2('B');
int *vfptr = new int;
int *pf = new int;
memcpy(vfptr, &b, 4);
memcpy(pf, reinterpret_cast<int *>(*vfptr)+2, 4);
void (*pfun)() = reinterpret_cast<void (*)()>(*pf);
pfun();
char *pa_val = new char;
int *pb_val = new int;
memcpy(pa_val, reinterpret_cast<int *>(&b)+1, sizeof(char));
memcpy(pb_val, reinterpret_cast<int *>(&b)+2, sizeof(int));
cout<<*pa_val<<endl;59 cout<<*pb_val<<endl;
memcpy(pb_val, reinterpret_cast<int *>(&b)+3, sizeof(int));
//存在內存對齊的問題
memcpy(pa_val, reinterpret_cast<int *>(&b)+4, sizeof(char));
cout<<*pb_val<<endl;66 cout<<*pa_val<<endl;
/*
cout<<"<<<<<<<<<<<<<<"<<endl;
*pa_val = 'B';
*pb_val = 999;
memcpy(reinterpret_cast<int *>(&b)+1, pa_val, sizeof(char));
memcpy(reinterpret_cast<int *>(&b)+2, pb_val, 4);
cout<<b.geta_Val()<<endl;
cout<<b.getb_Val()<<endl;
*/
return 0;
}