您好,登錄后才能下訂單哦!
C++中虛函數的調用及對象的內部布局是什么樣的,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
這次我依然用分析C++代碼編譯后生成的匯編代碼來說明C++中虛函數調用的實現方法,順便也說明一下C++中的對象內部布局。下面所有的匯編代碼都是用VC2005編譯出來的。雖然,不同的編譯器可能會編譯出不同的結果,對象的內部布局也不盡相同;但是,只要是符合C++標準的編譯器,編譯結果和對象的內部布局應該是大同小異。
首先,是一個有著簡單繼承關系的兩個類: class CBase
{
public:
virtual void VFun1() = 0;
virtual void VFun2() = 0;
void Fun1();
};
// 這里僅僅是為了生成函數的匯編代碼,因此函數體為空
void CBase::Fun1()
{
}
class CDerived : public CBase
{
public:
virtual void VFun1();
virtual void VFun2();
void Fun2();
private:
int m_iValue1;
int m_iValue2;
};
// 這里僅僅是為了生成函數的匯編代碼,因此函數體為空
void CDerived::VFun1()
{
}
// 這里僅僅是為了生成函數的匯編代碼,因此函數體為空
void CDerived::VFun2()
{
}
// 這里是為了分析對象的內部布局,因此僅僅是給成員變量賦值
void CDerived::Fun2()
{
m_iValue1 = 13;
m_iValue2 = 13;
}
現在用下面的代碼來調用成員函數:
CDerived derived;
// 用對象調用虛函數
derived.VFun1();
derived.VFun2();
// 用對象調用非虛函數
derived.Fun1();
derived.Fun2();
// 用指向派生類的基類的指針調用虛函數,實現多態
CBase *pTest = &derived;
pTest->VFun1();
pTest->VFun2();
下面就是用VC2005編譯上面的代碼后生成的匯編代碼:
CDerived derived;
0041195E lea ecx,[derived]
00411961 call CDerived::CDerived (411177h)
// 代碼段1
derived.VFun1();
00411966 lea ecx,[derived]
00411969 call CDerived::VFun1 (411078h)
derived.VFun2();
0041196E lea ecx,[derived]
00411971 call CDerived::VFun2 (4111B8h)
derived.Fun1();
00411976 lea ecx,[derived]
00411979 call CBase::Fun1 (411249h)
derived.Fun2();
0041197E lea ecx,[derived]
00411981 call CDerived::Fun2 (4111BDh)
// 代碼段2
CBase *pTest = &derived;
00411986 lea eax,[derived]
00411989 mov dword ptr [pTest],eax
pTest->VFun1();
0041198C mov eax,dword ptr [pTest] // 行1
0041198F mov edx,dword ptr [eax] // 行2
00411991 mov esi,esp
00411993 mov ecx,dword ptr [pTest]
00411996 mov eax,dword ptr [edx] // 行3
00411998 call eax // 行4
0041199A cmp esi,esp
0041199C call @ILT+495(__RTC_CheckEsp) (4111F4h)
pTest->VFun2();
004119A1 mov eax,dword ptr [pTest]
004119A4 mov edx,dword ptr [eax]
004119A6 mov esi,esp
004119A8 mov ecx,dword ptr [pTest]
004119AB mov eax,dword ptr [edx+4] // 行5
004119AE call eax
004119B0 cmp esi,esp
004119B2 call @ILT+495(__RTC_CheckEsp) (4111F4h)
通過對代碼段1的觀察我們可以發現:通過對象調用類的虛成員函數和調用非虛成員函數是相同的(對調用成員函數的匯編代碼的分析可以看我的那篇《淺析C++中的this指針》)。也就是說,用對象是無法實現多態的。
下面主要來分析實現多態的代碼段2。
行1、將pTest指針指向的地址前2個字(4個字節,也就是32位系統中一個指針的大小)的內容當成一個指針放到eax寄存器中
行2、將eax寄存器中的指針的值放入edx寄存器
行3、將dex寄存器中的指針的值放入eax寄存器
行4、調用eax寄存器指向的函數
這樣分析似乎對怎樣調用對象derived的虛函數VFun1()并不是很清楚。那么我們先來看下面的這張圖:
這張圖是一個假設的對象derived在內存中的內部布局圖。指針pTest指向對象derived,而對象derived的前4個字節是一個虛表指針,指向虛函數表。
看著這張圖再來分析上面的匯編代碼就會清晰很多:
行1、取得虛表指針值放入eax寄存器中
行2、取得虛表指針的值放入edx寄存器中
行3、取得虛表指針指向的地址的值(也就是VFun1)放入eax寄存器中
行4、調用eax寄存器指向的函數
行5證明了上面圖中對虛函數表的假設。第二個虛函數VFun2()的地址就是通過在第一虛函數VFun1()的地址加4(32位系統中一個指針的大小)而得到的。
通過上面的分析,可以得出C++中虛函數的調用方法:首先,取得對象中的虛表指針;然后,通過虛表指針找到相應的虛表;最后,通過在虛表內的偏移量找到相應的函數來調用。
下面通過分析類CDerived的非虛成員函數Fun2()來證明上面圖中虛函數表指針的存在。
void CDerived::Fun2()
{
004118F0 push ebp
004118F1 mov ebp,esp
004118F3 sub esp,0CCh
004118F9 push ebx
004118FA push esi
004118FB push edi
004118FC push ecx
004118FD lea edi,[ebp-0CCh]
00411903 mov ecx,33h
00411908 mov eax,0CCCCCCCCh
0041190D rep stos dword ptr es:[edi]
0041190F pop ecx
00411910 mov dword ptr [ebp-8],ecx
m_iValue1 = 13;
00411913 mov eax,dword ptr [this] // 行6
00411916 mov dword ptr [eax+4],0Dh // 行7
m_iValue2 = 13;
0041191D mov eax,dword ptr [this]
00411920 mov dword ptr [eax+8],0Dh
}
00411927 pop edi
00411928 pop esi
00411929 pop ebx
0041192A mov esp,ebp
0041192C pop ebp
0041192D ret
上面是類CDerived的非虛成員函數Fun2()的匯編代碼。可以看到,行6是將this指向的地址放入eax寄存器,而行7是給this指針指向的地址加4的地址賦值(具體的分析,可以看《淺析C++中的this指針》),而這個地址里面存放的是類CDerived的第一個成員變量。我們知道this指針是指向對象首地址的,那么為什么要給第一個成員變量賦值的時候要向后移動4個字節?答案是因為對象的前4個字節是用來存放虛表指針的。
下面的代碼是《淺析C++中的this指針》一文中的不含虛函數的類的C++代碼和編譯后的匯編代碼:
class CTest
{
public:
void SetValue();
private:
int m_iValue1;
int m_iValue2;
};
void CTest::SetValue()
{
m_iValue1 = 13;
m_iValue2 = 13;
}
void CTest::SetValue()
{
004117E0 push ebp
004117E1 mov ebp,esp
004117E3 sub esp,0CCh
004117E9 push ebx
004117EA push esi
004117EB push edi
004117EC push ecx
004117ED lea edi,[ebp-0CCh]
004117F3 mov ecx,33h
004117F8 mov eax,0CCCCCCCCh
004117FD rep stos dword ptr es:[edi]
004117FF pop ecx
00411800 mov dword ptr [ebp-8],ecx
m_iValue1 = 13;
00411803 mov eax,dword ptr [this] // 行8
00411806 mov dword ptr [eax],0Dh // 行9
m_iValue2 = 13;
0041180C mov eax,dword ptr [this]
0041180F mov dword ptr [eax+4],0Dh
}
00411816 pop edi
00411817 pop esi
00411818 pop ebx
00411819 mov esp,ebp
0041181B pop ebp
0041181C ret
通過行8、行9和行6、行7的比較就可以看出:類CTest的對象前4個字節存放的是自己的第一個成員變量;而類CDerived的對象從第5個字節開始才是存放的自己的第一個成員變量,它的前4個字節是用來存放虛表指針的。這再一次證明了上面圖中對象內部布局的正確
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。