1. 程式人生 > >C++中建構函式能呼叫虛擬函式嗎?

C++中建構函式能呼叫虛擬函式嗎?

      環境:XPSP3 VS2005

        今天黑總給應聘者出了一個在C++的建構函式中呼叫虛擬函式的問題,具體的題目要比標題複雜,大體情況可以看如下的程式碼:

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;

        首先回答標題的問題,呼叫當然是沒有問題的,但是獲得的是你想要的結果嗎?或者說你想要什麼樣的結果?

        有人說會輸出:

A::Fuction
A::Fuction

        如果是這樣,首先我們回顧下C++物件模型裡面的構造順序,在構造一個子類物件的時候,首先會構造它的基類,如果有多層繼承關係,實際上會從最頂層的基類逐層往下構造(虛繼承、多重繼承這裡不討論),如果是按照上面的情形進行輸出的話,那就是說在構造Base的時候,也就是在Base的建構函式中呼叫Fuction的時候,呼叫了子類A的Fuction,而實際上A還沒有開始構造,這樣函式的行為就是完全不可預測的,因此顯然不是這樣,實際的輸出結果是:

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


其實事情就是這麼簡單。