c++中的源程序:
int main() {
X x;
}
</div>
上面的类X没有定义构造函数,仅仅有一个int i。
下面为其汇编程序:
push ebp;ebp为一个寄存器,总是指向一个函数调用堆栈的栈底,作为基址,用偏移量来访问该调用栈上的变量,但这里没有任何变量要访问,因此不起作用
mov ebp, esp;这两句的作用是为了保存调用main之前堆栈的基址ebp的值,并将ebp指向main调用栈的栈底
push ecx;将寄存器ecx的值压栈, 栈顶指针esp向前移动4byte
;这句的作用,为即将要创建的对象预留了4byte的空间,并向里面写入ecx的值
; 8 : X x;
; 9 : }
xor eax, eax;eax也是一个寄存器,这里不起作用
mov esp, ebp;将栈顶指针移动到push ecx前的位置,即释放了4byte的空间
pop ebp;恢复基址到main调用之前的状态
ret 0;函数返回
</div>
通过汇编发现,通过push ecx,编译器将堆栈栈顶移动4byte,并将寄存器的ecx的值写入,类X只含有一个int,大小刚好为4byte,因此这一句可以看成是为对象x分配空间。而接下来并没有任何函数的调用,来对这一块区域进行适当的初始化。所以,在没有明确定义一个构造函数的时候,不会有任何的初始化操作。
下面再看一段c++程序:
int main() {
X x;
}
</div>
与上面相比,在类X里面增加了一个成员变量int j,类的大小变为8字节。
下面为对应汇编码:
push ebp
mov ebp, esp
sub esp, 8; 栈顶指针移动8byte,刚好等于类X的大小
; 9 : X x;
; 10 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
</div>
从汇编码看出,通过sub esp,8指令,堆栈确实留出了8byte的空间,刚好等于类X的大小,同样没有调用任何函数,来进行初始化操作。
所以,综上所述,在一个类没有明确定义构造函数的时候,编译器不会有任何的函数调用来进行初始化操作,仅仅是移动栈顶留出对象所需空间,也就是说,这种情况下,编译器根本不会提供默认的构造函数。
那么,书上说的由编译器提供默认的构造函数到底是怎么一回事呢?
下面看第一种情况,类里面有虚成员函数:
c++源码如下:
int main() {
X x;
}
</div>
析构函数为虚函数
下面是main函数对应的汇编码:
push ebp
mov ebp, esp
sub esp, 12 ; 为对象x预留12byte的空间,成员变量int i,int j占8byte,由于有虚函数,因此vptr指针占4byte
; 14 : X x;
lea ecx, DWORD PTR _x$[ebp];获取x对象的首地址,存入ecx寄存器
call ??0X@@QAE@XZ;这里调用x的构造函数
; 15 : }
lea ecx, DWORD PTR _x$[ebp];获取对象x的首地址
call ??1X@@UAE@XZ ; 调用析构函数
xor eax, eax
mov esp, ebp
pop ebp
ret 0
</div>
可以看到,对象x的构造函数被调用了,编译器确实合成了默认的构造函数。
下面是构造函数的汇编码:
从上面可以看出,类里面含有虚函数时,在没有明确定义构造函数时,编译器确实会为我们提供一个默认的构造函数。因此当一个类继承自虚基类时,也满足上面的情形。
接下来是第二种情形,类Y继承自类X,X明确定义了一个默认的构造函数(并非编译器提供),而类Y不定义任何构造函数:
先来看看c++源码:
in