總所周知,構造函數是對象重要的組成部分,承擔了對象的初始化工作。本文主要講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構造函數的默認構造函數,繼承下的構造函數,有虛函數的構造函數)的反匯編會在下面的系列繼續。
這是我第一次寫博客,請大家多多指導,謝謝大家。