c++中的源程序:
int main() {
X x;
}
下面為其匯編程序:
push ebp;ebp為一個寄存器,總是指向一個函數(shù)調(diào)用堆棧的棧底,作為基址,用偏移量來訪問該調(diào)用棧上的變量,但這里沒有任何變量要訪問,因此不起作用
mov ebp, esp;這兩句的作用是為了保存調(diào)用main之前堆棧的基址ebp的值,并將ebp指向main調(diào)用棧的棧底
push ecx;將寄存器ecx的值壓棧, 棧頂指針esp向前移動4byte
;這句的作用,為即將要創(chuàng)建的對象預留了4byte的空間,并向里面寫入ecx的值
; 8 : X x;
; 9 : }
xor eax, eax;eax也是一個寄存器,這里不起作用
mov esp, ebp;將棧頂指針移動到push ecx前的位置,即釋放了4byte的空間
pop ebp;恢復基址到main調(diào)用之前的狀態(tài)
ret 0;函數(shù)返回
下面再看一段c++程序:
int main() {
X x;
}
下面為對應匯編碼:
push ebp
mov ebp, esp
sub esp, 8; 棧頂指針移動8byte,剛好等于類X的大小
; 9 : X x;
; 10 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
所以,綜上所述,在一個類沒有明確定義構造函數(shù)的時候,編譯器不會有任何的函數(shù)調(diào)用來進行初始化操作,僅僅是移動棧頂留出對象所需空間,也就是說,這種情況下,編譯器根本不會提供默認的構造函數(shù)。
那么,書上說的由編譯器提供默認的構造函數(shù)到底是怎么一回事呢?
下面看第一種情況,類里面有虛成員函數(shù):
c++源碼如下:
int main() {
X x;
}
下面是main函數(shù)對應的匯編碼:
push ebp
mov ebp, esp
sub esp, 12 ; 為對象x預留12byte的空間,成員變量int i,int j占8byte,由于有虛函數(shù),因此vptr指針占4byte
; 14 : X x;
lea ecx, DWORD PTR _x$[ebp];獲取x對象的首地址,存入ecx寄存器
call ??0X@@QAE@XZ;這里調(diào)用x的構造函數(shù)
; 15 : }
lea ecx, DWORD PTR _x$[ebp];獲取對象x的首地址
call ??1X@@UAE@XZ ; 調(diào)用析構函數(shù)
xor eax, eax
mov esp, ebp
pop ebp
ret 0
下面是構造函數(shù)的匯編碼:
從上面可以看出,類里面含有虛函數(shù)時,在沒有明確定義構造函數(shù)時,編譯器確實會為我們提供一個默認的構造函數(shù)。因此當一個類繼承自虛基類時,也滿足上面的情形。
接下來是第二種情形,類Y繼承自類X,X明確定義了一個默認的構造函數(shù)(并非編譯器提供),而類Y不定義任何構造函數(shù):
先來看看c++源碼:
class Y : public X{//Y繼承自X
private:
int i;
};
int main() {
Y y;
}
下面是main函數(shù)對應的匯編碼:
push ebp
mov ebp, esp
sub esp, 12 ; 為對象y預留12byte空間,y自身成員變量int i占4byte 父類中的成員變量int i int j占8byte
; 20 : Y y;
lea ecx, DWORD PTR _y$[ebp];獲取對象y的首地址,存入寄存器ecx
call ??0Y@@QAE@XZ;調(diào)用對象y的構造函數(shù)
; 21 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
下面是編譯器提供的y對象默認構造函數(shù)的匯編碼:
下面是父類X的構造函數(shù)匯編碼:
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx; ecx中存有對象y的首地址
; 8 : i = 0;
mov eax, DWORD PTR _this$[ebp];對象y首地址給寄存器eax
mov DWORD PTR [eax], 0;初始化父類中的變量i
; 9 : j = 1;
mov ecx, DWORD PTR _this$[ebp];對象y首地址給寄存器ecx
mov DWORD PTR [ecx+4], 1;初始化父類中的變量j,在對象y的內(nèi)存空間中,從首地址開始的8比特用來存儲繼承自父對象的成員變量,后4byte用來存儲自己的成員變量
;由于首地址存儲了父類成員變量i,因此內(nèi)存地址要從對象y的首地址要移動4byte,才能找到父類成員變量j所處位置
; 10 : }
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
如果父類X中也沒有定義任何構造函數(shù)會怎樣?
下面是c++源碼:
};
class Y : public X{//Y繼承自X
private:
int i;
};
int main() {
Y y;
}
下面是main函數(shù)匯編碼:
push ebp
mov ebp, esp
sub esp, 12 ; 和剛才一樣,為對象y預留12byte
; 17 : Y y;
; 18 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
那么,要是父類中帶參數(shù)的構造函數(shù),而子類中沒有構造函數(shù)呢?這時候編譯器會報錯。
下面看第三種情況,類Y中包含成員對象X,成員對象有顯示定義的默認構造函數(shù),而類Y沒有任何構造函數(shù):
先看c++源碼:
push ebp
mov ebp, esp
sub esp, 12 ; 和剛才一樣,為對象y預留12byte
; 17 : Y y;
; 18 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
push ebp
mov ebp, esp
sub esp, 12 ; 為對象y預留12byte 成員對象的變量占8byte 對象y自身占變量占4byte 成員對象包含在對象y中
; 22 : Y y;
lea ecx, DWORD PTR _y$[ebp];對象y的首地址存入ecx
call ??0Y@@QAE@XZ;調(diào)用對象y的構造函數(shù),由編譯器提供的默認構造函數(shù)
; 23 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
對象y的構造函數(shù)匯編碼:
成員對象x的構造函數(shù)匯編碼:
; 7 : X() {
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx;ecx中存有成員對象x的起始地址
; 8 : i = 0;
mov eax, DWORD PTR _this$[ebp];成員對象x的起始地址給eax寄存器
mov DWORD PTR [eax], 0;初始化成員對象x中額成員變量i
; 9 : j = 0;
mov ecx, DWORD PTR _this$[ebp];成員對象x的起始地址給ecx寄存器
mov DWORD PTR [ecx+4], 0;初始化成員對象x中額成員變量j 加4的原因是j的地址偏離了成員對象x起始地址4byte(即成員對象x的成員變量i的字節(jié)數(shù))
; 10 : }
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
下面是c++源碼:
};
class Y {
private:
int i;
X x;//x成員對象
};
int main() {
Y y;
}
push ebp
mov ebp, esp
sub esp, 12 ; 為對象預留12byte空間
; 18 : Y y;
; 19 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
那要是成員對象x有帶參數(shù)的構造函數(shù)(即非默認構造函數(shù)),而對象y沒有任何構造函數(shù)呢?此時,編譯器會報錯。
這種情形和前一種情形很相似。
綜合以上的情況,可以總結出,對于一個類不含任何構造函數(shù),而編譯器會提供默認的構造函數(shù),有一下3種情形:
1 類本身函數(shù)虛成員函數(shù)或者繼承自虛基類
2 類的基類有構造函數(shù),并且基類構造函數(shù)還是顯示定義的默認構造函數(shù)(非編譯器提供),若基類的構造函數(shù)帶有參數(shù)(即非默認構造函數(shù)),編譯器報錯
3 這種情況和上一種相似,類的成員對象有構造函數(shù),并且成員對象的構造函數(shù)還是顯示定義的默認構造函數(shù)(非編譯器提供);若成員對象的構造函數(shù)帶有參數(shù)(即非默認構造函數(shù)),編譯器報錯。
以上參考了《VC++深入詳解》里面的知識點,還有自己的分析,歡迎指正
新聞熱點
疑難解答