; 32 : yp->c();
mov ecx, DWORD PTR _yp$[ebp];将yp所指向的堆对象的首地址给ecx
mov edx, DWORD PTR [ecx];将堆对象首地址的内容给edx,即将子类vptr指向的vtable首地址给edx
mov ecx, DWORD PTR _yp$[ebp];将yp所指向的堆对象首地址给ecx,作为隐含参数传递给虚成员函数c
mov eax, DWORD PTR [edx+8];将偏移子类vtable首地址8byte处内存的内容给eax,即将虚函数c的地址给eax(这里,虚函数b的地址同样位于偏移子类Y的vtable首地址8byte处)
call eax;调用虚成员函数c
对象切片
如果进行向上转型的时候不是用传地址或者引用,而是用传值,那么就会发生对象切片,即派生类对象中原有的部分被切除,只保留了基类的部分。
下面是c++源码:
class X {
private:
int i;
public:
virtual void a() {
i = 1;
}
virtual void b() {
i = 2;
}
};
class Y : public X {
private:
int i;
public:
virtual void c() {//新定义的虚函数
i = 3;
}
void b() {//重写父类中的虚函数
i = 4;
}
};
void f(X x) {//用传值的形式进行向上转换
x.b();
}
int main() {
Y y;
f(y);
}
下面是main函数的汇编码:
; 28 : int main() {
push ebp
mov ebp, esp
sub esp, 16 ; 为对象y预留16byte的空间
; 29 : Y y;
lea ecx, DWORD PTR _y$[ebp];将y的首地址给ecx,转为隐含参数传递给y的构造函数
call ??0Y@@QAE@XZ;调用y的构造函数
; 30 : f(y);
sub esp, 8;//由于对象传值,要进行拷贝,产生临时对象,这里为临时对象预留8byte的空间(类X的大小)
mov ecx, esp;//将临时对象的首地址给ecx,作为隐含参数传递给拷贝函数
lea eax, DWORD PTR _y$[ebp];将对象y的首地址给eax,作为参数给拷贝函数
push eax;压栈,传递参数
call ??0X@@QAE@ABV0@@Z;调用类X的拷贝函数
call ?f@@YAXVX@@@Z ; 调用函数f
add esp, 8;释放刚才的临时对象占用的8byte空间
; 31 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
从汇编吗中可以看出,临时对象的大小为父类X的大小,调用的拷贝函数也是父类X的拷贝函数。
下面是父类X的拷贝函数汇编码:
??0X@@QAE@ABV0@@Z PROC ; X::X, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx;压栈,为存对象首地址预留4byte空间
mov DWORD PTR _this$[ebp], ecx;ecx中保存临时对象首地址,放到刚才预留的空间
mov eax, DWORD PTR _this$[ebp];将临时对象首地址给ecx
mov DWORD PTR [eax], OFFSET ??_7X@@6B@;将类X的vtable首地址存到临时对象首地址所指向的内存 即初始化临时对象的vptr指针
mov ecx, DWORD PTR _this$[ebp];将临时对象的首地址给ecx
mov edx, DWORD PTR ___that$[ebp];将y的首地址给edx
mov eax, DWORD PTR [edx+4];将偏移y首地址4byte处内存内容给edx,即将y包含的父对象中的成员变量i的值给edx
mov DWORD PTR [ecx+4], eax;将eax的值给偏移临时对象首地址4byte处内存,即将eax的值给临时对象的成员变量i
mov eax, DWORD PTR _this$[ebp];将临时对象的首地址给eax,作为返回值。构造函数总是返回对象首地址
mov esp, ebp
pop ebp
ret 4










