??0Y@@QAE@XZ ENDP
下面是父类构造函数汇编码:
??0X@@QAE@XZ PROC ; X::X, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx;压栈的目的是为了存放this指针(父对象对象首地址)预留空间
mov DWORD PTR _this$[ebp], ecx;将父对象对象首地址(ecx中保存)放入刚才预留空间
mov eax, DWORD PTR _this$[ebp];将父对象首地址给寄存器eax
mov DWORD PTR [eax], OFFSET ??_7X@@6B@;将vtable(??_7X@@6B@ 和子类不同)首地址赋给父对象首地址处的内存 即初始化父对象的vptr指针
mov eax, DWORD PTR _this$[ebp];将父对象的首地址传给eax,作为返回值。构造函数总是返回对象首地址
mov esp, ebp
pop ebp
ret 0
??0X@@QAE@XZ ENDP
从上面子类和父类的构造函数汇编码可以看出来,子对象包含父对象,在构造子对象的时候先构造父对象(子对象构造函数先
调用父对象构造函数)。而且父对象的首地址和子对象的首地址一样(通过汇编码中ecx传递的值可以看出来),因此父对象和子
对象的vptr指针位于同一处。所以,在构造对象的构成中,vptr指针先被初始化指向父对象的vtable首地址(在父对象构造函数
中),最后又被初始化为指向子对象的vtable首地址(在子对象的构造函数中)。因此,在涉及继承的时候,vptr指针的值由最后
调用的构造函数决定。
在构造函数调用虚函数机制失效,也就是说,在构造函数中调用虚函数总是本地版本(析构函数中也是一样)
c++源码如下源码如下:
class X {
private:
int i;
public:
virtual void f(int ii) {
i = ii;
}
X() {
f(1);
}
};
class Y : public X {//Y继承自X
private:
int j;
public:
virtual void f(int ii) {
j = ii;
}
Y() {
f(2);
}
};
int main() {
Y y;
}
下面主要来看父类X和子类Y中的构造函数的汇编码:
子类子类Y的构造函数汇编码的构造函数汇编码:
??0Y@@QAE@XZ PROC ; Y::Y, COMDAT
; _this$ = ecx
; 20 : Y() {
push ebp
mov ebp, esp
push ecx;压栈的目的是为存放this指针(在ecx寄存器里面存放了子对象首地址)预留空间
mov DWORD PTR _this$[ebp], ecx;将子对象首地址存入刚才预留空间
mov ecx, DWORD PTR _this$[ebp];将子类首地址作为隐含参数传给父对象构造器(子对象首地址和父对象首地址一样)
call ??0X@@QAE@XZ ; 调用父类构造器
mov eax, DWORD PTR _this$[ebp];将子对象首地址传给寄存器eax