標籤:文法 知識 replace ble source gic call 題目 地址
環境:XPSP3 VS2005
今天黑總給應聘者出了一個在C++的建構函式中調用虛函數的問題,具體的題目要比標題複雜,大體情況可以看如下的代碼:
[cpp] view plain copy
- class Base
- {
- public:
- Base()
- {
- Fuction();
- }
-
- virtual void Fuction()
- {
- cout << "Base::Fuction" << endl;
- }
- };
-
- class A : public Base
- {
- public:
- A()
- {
- Fuction();
- }
-
- virtual void Fuction()
- {
- cout << "A::Fuction" << endl;
- }
- };
-
- // 這樣定義一個A的對象,會輸出什嗎?
- A a;
首先回答標題的問題,調用當然是沒有問題的,但是獲得的是你想要的結果嗎?或者說你想要什麼樣的結果?
有人說會輸出:
[html] view plain copy
- A::Fuction
- A::Fuction
如果是這樣,首先我們回顧下C++物件模型裡面的構造順序,在構造一個子類對象的時候,首先會構造它的基類,如果有多層繼承關係,實際上會從最頂層的基類逐層往下構造(虛繼承、多重繼承這裡不討論),如果是按照上面的情形進行輸出的話,那就是說在構造Base的時候,也就是在Base的建構函式中調用Fuction的時候,調用了子類A的Fuction,而實際上A還沒有開始構造,這樣函數的行為就是完全不可預測的,因此顯然不是這樣,實際的輸出結果是:
[html] view plain copy
- Base::Fuction
- A::Fuction
據說在Java中是上一種輸出(感覺有點匪夷所思)。
我們來單步看一下到底發生了什嗎?在A的建構函式裡面首先會去調用Base的建構函式,Base的建構函式如下:
class Base
{
public:
Base()
00411600 push ebp
00411601 mov ebp,esp
00411603 sub esp,0CCh
00411609 push ebx
0041160A push esi
0041160B push edi
0041160C push ecx
0041160D lea edi,[ebp-0CCh]
00411613 mov ecx,33h
00411618 mov eax,0CCCCCCCCh
0041161D rep stos dword ptr es:[edi]
0041161F pop ecx
00411620 mov dword ptr [ebp-8],ecx
00411623 mov eax,dword ptr [this]
00411626 mov dword ptr [eax],offset Base::`vftable‘ (41770Ch)
{
Fuction();
0041162C mov ecx,dword ptr [this]
0041162F call Base::Fuction (4111A9h)
}
00411634 mov eax,dword ptr [this]
00411637 pop edi
00411638 pop esi
00411639 pop ebx
0041163A add esp,0CCh
00411640 cmp ebp,esp
00411642 call @ILT+460(__RTC_CheckEsp) (4111D1h)
00411647 mov esp,ebp
00411649 pop ebp
0041164A ret
從單步跟蹤來看,注意黑色加粗的那部分彙編代碼,ecx中存放的是對象的地址(0x0012ff60,我的機器上的情況看,有圖有真相),首先是設定vtable的地址到對象的前四個位元組(不同的編譯器可能不同),然後就直接調用了Base::Fuction函數,並沒有走虛機制,而我們此時看虛表中的狀態,虛表已經填充的是0x4111a9,注意虛表的地址0x0041770c,而此時對象地址0x0012FF60前四個位元組存放的正是0x0041770c。
繼續跟蹤,流程又回到A的建構函式中,再次注意加粗部分的代碼,從基類Base的建構函式返回後,在A的建構函式中,重設了虛表指標,現在的虛表指標是(0x417700h),同樣調用Fuction的時候直接調用了A::Fuction函數,並沒有使用虛機制,而且此時虛表0x417700h指向的位置存放的0x41110e正是A::Fuction的地址。
class A : public Base
{
public:
A()
00411590 push ebp
00411591 mov ebp,esp
00411593 sub esp,0CCh
00411599 push ebx
0041159A push esi
0041159B push edi
0041159C push ecx
0041159D lea edi,[ebp-0CCh]
004115A3 mov ecx,33h
004115A8 mov eax,0CCCCCCCCh
004115AD rep stos dword ptr es:[edi]
004115AF pop ecx
004115B0 mov dword ptr [ebp-8],ecx
004115B3 mov ecx,dword ptr [this]
004115B6 call Base::Base (411140h)
004115BB mov eax,dword ptr [this]
004115BE mov dword ptr [eax],offset A::`vftable‘ (417700h)
{
Fuction();
004115C4 mov ecx,dword ptr [this]
004115C7 call A::Fuction (41110Eh)
}
004115CC mov eax,dword ptr [this]
004115CF pop edi
004115D0 pop esi
004115D1 pop ebx
004115D2 add esp,0CCh
004115D8 cmp ebp,esp
004115DA call @ILT+460(__RTC_CheckEsp) (4111D1h)
004115DF mov esp,ebp
004115E1 pop ebp
004115E2 ret
其實事情就是這麼簡單。
http://blog.csdn.net/magictong/article/details/6734241
C++中建構函式能調用虛函數嗎?(答案是文法可以,輸出錯誤),但Java裡居然可以