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

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

下面只给出调用虚函数时的汇编代码:


; 28  :   xp->a();

  mov  edx, DWORD PTR _xp$[ebp];将xp所指向的堆对象首地址给edx
  mov  eax, DWORD PTR [edx];将堆对象首地址里面的内容给eax,即将vptr指向的vtable首地址给eax
  mov  ecx, DWORD PTR _xp$[ebp];将xp所指向的堆对象首地址给ecx,作为隐含参数传递给即将调用的虚成员函数
  mov  edx, DWORD PTR [eax];将vtable首地址里面的内容给edx,即将虚函数a的地址给edx(这里,虚函数a的地址位于父类X的vtable首地址处)
  call  edx;调用虚成员函数a

; 29  :   xp->b();

  mov  eax, DWORD PTR _xp$[ebp];将xp所指堆对象的首地址给eax
  mov  edx, DWORD PTR [eax];将堆对象首地址的内容给edx,即将vptr指向的vtable首地址给edx
  mov  ecx, DWORD PTR _xp$[ebp];将xp所指堆对象的首地址给ecx
  mov  eax, DWORD PTR [edx+4];将偏移vtable首地址4byte处内存内容给eax,即将虚函数b的地址给eax(这里,虚函数b的地址位于偏移父类X的vtable首地址4byte处)
  call  eax;调用虚成员函数b

; 30  :   yp->a();

  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,作为隐含参数传递给虚成员函数a
  mov  eax, DWORD PTR [edx];将子类vtable首地址处的内容给eax,即将虚函数a的地址给eax(这里,虚函数a的地址同样位于子类Y的vtable首地址处)
  call  eax;调用虚成员函数a

; 31  :   yp->b();

  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,作为隐含参数传递给虚成员函数b
  mov  eax, DWORD PTR [edx+4];将偏移子类vtable首地址4byte处内存的内容给eax,即将虚函数b的地址给eax(这里,虚函数b的地址同样位于偏移子类Y的vtable首地址4byte处)
  call  eax;调用虚成员函数b
; 32  :   //yp->c();

从汇编码可以看出,a,b虚函数在子类vtable和父类table中的位置是一样的(从它们相对于自己所在vtable的偏移量可以看出)。这就保证了不论对象实际的类型是什么,编译器总能使用同样的偏移量来调用虚函数。假如不这么做,也就是说虚函数a,b在子类Y的vtable中的位置和在父类X的vtable中的位置不一样,由于向上转型,编译器只针对父类工作,也就是对虚函数a,b的调用只会根据父类X的vtable来确定偏移量,那么在实际运行的时候就会出错,实际的子对象根本调用不到正确的函数,多态失效。

在上面的例子中,如果将yp转为实际的类型调用c,我们会看到编译器形成的偏移量为8byte,汇编