从汇编看c++中的多态详解

2020-01-06 15:23:17王振洲


; 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