如果类有继承关系,构造函数又如何初始化vptr指针呢?

  下面是c++源码:

class X {
private:
    int i;
public:
    virtual void f() {}
};

class Y : public X {//Y继承自X
private:
   int j;
};

 


int main() {
 Y y;
}

  下面是main函数中的汇编码:

_main    PROC

; 16   : int main() {

    push    ebp
    mov    ebp, esp
    sub    esp, 12                    ; 为对象y预留12 byte的空间 vptr指针4byte 父类成员变量4byte 子类成员变量4byte

; 17   :  Y y;

    lea    ecx, DWORD PTR _y$[ebp];获取对象y的首地址(即this指针),作为隐含参数传递给构造函数
    call    ??0Y@@QAE@XZ;调用y的构造函数 虽然y没有显示定义构造函数,但由于其含有虚成员函数,编译器提供默认构造函数

; 18   : }

    xor    eax, eax
    mov    esp, ebp
    pop    ebp
    ret    0
_main    ENDP

  下面是子类构造函数汇编码:

??0Y@@QAE@XZ PROC                    ; Y::Y, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    push    ecx;//压栈ecx的目的是存放this指针
    mov    DWORD PTR _this$[ebp], ecx;将this指针(即对象首地址)放到刚才预留空间 ecx里面存放对象首地址
    mov    ecx, DWORD PTR _this$[ebp];将对象首地址给ecx 作为隐含参数传递给父类构造函数
    call    ??0X@@QAE@XZ;调用父类构造函数
    mov    eax, DWORD PTR _this$[ebp];将y的首地址给寄存器eax
    mov    DWORD PTR [eax], OFFSET ??_7Y@@6B@;将y的vtable(??_7Y@@6B@)首地址赋给y对象首地址所指内存 即初始化子类vptr指针
    mov    eax, DWORD PTR _this$[ebp];将y首地址给eax,作为返回值。构造函数总是返回对象首地址
    mov    esp, ebp
    pop    ebp
    ret    0
??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指针的值由后调用的构造函数决定。