深入C++的默認構造函數1


      總所周知,構造函數是對象重要的組成部分,承擔了對象的初始化工作。本文主要講C++下對象的默認構造函數的反匯編代碼,或許,這沒什么用處,但是,知其然,還要知其所以然吧,了解底層,將對我們更好地掌握知識有很大幫助。打牢基礎,將更有利於我們的成長。“勿在浮沙築高台”-------侯捷。

     當一個對象沒有聲明構造函數的時候,編譯器會暗中為對象生成一個默認構造函數(Default Constructor),被暗中生成的的默認構造函數將是一個trivial(無用的,淺薄無能的)的函數。(摘自“深度探索C++對象模型 P40”)。下面我們來看看編譯器生成的默認構造函數。

例子1:

class A
{
public:
    void print(){};
};

int main()
{
    A a;
    a.print();
    return 0;
};

在vs2008下main方法的匯編代碼如下所示(按F5進入調試模式,之后“Alt+8”即可跳出代碼和匯編的混合代碼):

int main()
  {
  013A13A0  push        ebp  
  013A13A1  mov         ebp,esp 
  013A13A3  sub         esp,0CCh 
  013A13A9  push        ebx  
  013A13AA  push        esi  
  013A13AB  push        edi  
  013A13AC  lea         edi,[ebp-0CCh] 
  013A13B2  mov         ecx,33h 
  013A13B7  mov         eax,0CCCCCCCCh 
  013A13BC  rep stos    dword ptr es:[edi] 
      A a;
      a.print();
  013A13BE  lea         ecx,[a] 
  013A13C1  call        A::print (13A100Fh) 
      return 0;
  013A13C6  xor         eax,eax 
  };

 下面是這段匯編代碼的解釋,參考:http://johnlxj.diandian.com/?tag=edi:

push ebp;將ebp壓入棧,ebp主要用來存儲當前棧幀的棧底指針,所有的局部變量都是用相對於ebp的偏移來使用的。

move ebp,esp

sub esp,oCCh;這兩句的意思是是將當前棧頂作為這一棧幀的基值。並且為本函數分配了0CCh*4個字節的棧空間。因為棧頂往上走了cc步。

push ebx

push esi

push edi;這三句的意思是將上個函數所使用的寄存器的值壓入棧中,即保存上個方法所使用的寄存器值,為了執行完本函數后能返回去。

lea edi, [ebp-0CCh]

mov ecx,33h

mov eax,0CCCCCCCCh

rep stos dword ptr es:[edi]; 上面4句代碼的意思是將ebp到0CCh的棧空間(33h * 4字節)通過rep(循環指令)置為0CCh。

通過上面的匯編,我們可以知道對於一個這樣簡單的類(只聲明了一個方法的類),編譯器生成的代碼甚至沒有調用它的構造函數(應該編譯器沒有暗中合成它的構造函數)。

例子2:

當我們將A的聲明改為:

class A
{
public:

A(){};
void print(){};
};

反匯編的代碼如下所示:

int main()
{
00B613B0  push        ebp  
00B613B1  mov         ebp,esp 
00B613B3  sub         esp,0CCh 
00B613B9  push        ebx  
00B613BA  push        esi  
00B613BB  push        edi  
00B613BC  lea         edi,[ebp-0CCh] 
00B613C2  mov         ecx,33h 
00B613C7  mov         eax,0CCCCCCCCh 
00B613CC  rep stos    dword ptr es:[edi] 
    A a;
00B613CE  lea         ecx,[a] 
00B613D1  call        A::A (0B611D6h) 
    a.print();
00B613D6  lea         ecx,[a] 
00B613D9  call        A::print (0B6100Fh) 
    return 0;
00B613DE  xor         eax,eax 
};

A的構造函數反匯編如下:

  A(){};
00B61440  push        ebp 
00B61441  mov         ebp,esp 
00B61443  sub         esp,0CCh 
00B61449  push        ebx  
00B6144A  push        esi  
00B6144B  push        edi  
00B6144C  push        ecx  
00B6144D  lea         edi,[ebp-0CCh] 
00B61453  mov         ecx,33h 
00B61458  mov         eax,0CCCCCCCCh 
00B6145D  rep stos    dword ptr es:[edi] 
00B6145F  pop         ecx  
00B61460  mov         dword ptr [ebp-8],ecx 
00B61463  mov         eax,dword ptr [this] 
00B61466  pop         edi  
00B61467  pop         esi  
00B61468  pop         ebx  
00B61469  mov         esp,ebp 
00B6146B  pop         ebp  
00B6146C  ret

對照前面的解釋,A::A()是啥事都沒做(在將申請的棧空間置為0后就開始將退出函數),這段匯編代碼或許說明我們聲明個空構造函數是件沒意義的事(當然,帶有成員初始化列表的空構造函數不算)。

例子3:

下面再給類的定義添些東西:

class A
{
public:

A(){};
void print(){};

private:

int m_nValue;
};

直接貼它的構造函數:

  A(){};
00E21420  push        ebp  
00E21421  mov         ebp,esp 
00E21423  sub         esp,0CCh 
00E21429  push        ebx  
00E2142A  push        esi  
00E2142B  push        edi  
00E2142C  push        ecx  
00E2142D  lea         edi,[ebp-0CCh] 
00E21433  mov         ecx,33h 
00E21438  mov         eax,0CCCCCCCCh 
00E2143D  rep stos    dword ptr es:[edi] 
00E2143F  pop         ecx  
00E21440  mov         dword ptr [ebp-8],ecx 
00E21443  mov         eax,dword ptr [this] 
00E21446  pop         edi  
00E21447  pop         esi  
00E21448  pop         ebx  
00E21449  mov         esp,ebp 
00E2144B  pop         ebp  
00E2144C  ret

 

和例子2的構造函數對比,代碼完全一樣,這說明類中的成員變量在初始化的時候需要是一個特定值的話,應該自己手動初始化,編譯器是不會負責這個事情的。但是,當我們類中有成員變量的時候,類構造的時候最好將成員變量初始化相應的初值,這是個良好的編程習慣,這次就先說到這里了,復雜的默認構造函數(成員變量有Default構造函數的默認構造函數,繼承下的構造函數,有虛函數的構造函數)的反匯編會在下面的系列繼續。

這是我第一次寫博客,請大家多多指導,謝謝大家。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM