Cpp7 C++的多态实现 -- 虚表

admin 2018年5月13日07:37:34评论919 views字数 4205阅读14分1秒阅读模式
摘要

总结:发现:定义了虚函数 对象大小会多出4个字节,多个虚函数也只有多一个4字节

多态的实现原理

#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{public:    int x;    virtual void Test()    {        printf("A /n");    }protected:private:};class B:public A{public:    int x;    void Test()    {        printf("B /n");    }protected:private:};void Fun(A* p){    p->Test();}int main(int argc, char* argv[]){    A a;    B b;    Fun(&b);    return 0;}//我们发现在这里 调用的test函数 是b的 因为fun方法传入的对象是b b继承自a 这里体现了多态//反编译31:   void Fun(A* p)32:   {00401050   push        ebp00401051   mov         ebp,esp00401053   sub         esp,40h00401056   push        ebx00401057   push        esi00401058   push        edi00401059   lea         edi,[ebp-40h]0040105C   mov         ecx,10h00401061   mov         eax,0CCCCCCCCh00401066   rep stos    dword ptr [edi]33:       p->Test();00401068   mov         eax,dword ptr [ebp+8]0040106B   mov         edx,dword ptr [eax]0040106D   mov         esi,esp0040106F   mov         ecx,dword ptr [ebp+8]00401072   call        dword ptr [edx]    //间接调用 + 虚表00401074   cmp         esi,esp00401076   call        __chkesp (00401240)34:   }@ILT+0(?Fun@@YAXPAVA@@@Z):00401005   jmp         Fun (00401050)@ILT+5(??0B@@QAE@XZ):0040100A   jmp         BA (00401100)@ILT+15(?Test@B@@UAEXXZ):00401014   jmp         BTest (00401140)@ILT+25(_main):0040101E   jmp         main (004010a0)

总结:

1. 当我们在类中定义虚函数时,就会产生虚表2. 多态的实现 间接调用+虚表

虚表

观察带有虚函数的对象大小

#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{public:    int x;    void Test()    {        printf("A /n");    }protected:private:};int main(int argc, char* argv[]){    A a;    printf("%d /n",sizeof(a));    return 0;}//结果是4
#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{public:    int x;    virtual void Test()    {        printf("A /n");    }protected:private:};int main(int argc, char* argv[]){    A a;    printf("%d /n",sizeof(a));    return 0;}//结果是 8
#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{public:    int x;    virtual void Test()    {        printf("A /n");    }    virtual void Test1()    {        printf("A /n");    }protected:private:};int main(int argc, char* argv[]){    A a;    printf("%d /n",sizeof(a));    return 0;}//结果还是8

发现:定义了虚函数 对象大小会多出4个字节,多个虚函数也只有多一个4字节

虚表的位置

#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{public:    int x;    virtual void Test()    {        printf("A /n");    }};class B:public A{public:    virtual void Test()    {        printf("B /n");    }};void Fun(A* a){    a->Test();}int main(int argc, char* argv[]){    A a;    B b;    Fun(&a);    Fun(&b);    return 0;}

通过vc6的监视器发现a对象的具体结构

Cpp7 C++的多态实现 -- 虚表

发现加了虚函数之后对象前面对了一个值 指向 0x00422fac

那么这里的值指向的就是虚表的位置

继续追踪
调出内存窗口查找此内存

Cpp7 C++的多态实现 -- 虚表

里面的值是 00401028 (小端存储)

vc6中 ctrl +g 跳转到对应反汇编位置

Cpp7 C++的多态实现 -- 虚表

这里指向的正好是A的test 方法
(此时多了个TEST1 是因为上次编译后的结果没有清理缓存 )

26:   void Fun(A* a)27:   {00401050   push        ebp00401051   mov         ebp,esp00401053   sub         esp,40h00401056   push        ebx00401057   push        esi00401058   push        edi00401059   lea         edi,[ebp-40h]0040105C   mov         ecx,10h00401061   mov         eax,0CCCCCCCCh00401066   rep stos    dword ptr [edi]28:       a->Test();//取参数也就是a对象的指针 到eax00401068   mov         eax,dword ptr [ebp+8] //读取eax也就是a对象的首地址 也就是虚表的位置 0040106B   mov         edx,dword ptr [eax]  0040106D   mov         esi,esp//传递this指针,到ecx0040106F   mov         ecx,dword ptr [ebp+8]//调用虚表中记录的函数位置 这里是第一个就直接是edx00401072   call        dword ptr [edx]00401074   cmp         esi,esp00401076   call        __chkesp (00401240)29:   }//虚表00401023   jmp         A::A (004010d0)00401028   jmp         A::Test (00401090)0040102D   jmp         A::Test1 (00401110)00401032   jmp         B::B (00401170)00401037   jmp         Fun (00401050)0040103C   jmp         B::Test (004011c0)00401041   jmp         A::A (00401200)

虚表的结构

据观察,虚表中存储的都是函数地址,每个地址占用4个字节,有几个虚函数,则就有几个地址

虚表的内容

子类没有重写时的值

#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{public:    int x;    virtual void Test()    {        printf("A /n");    }};class B:public A{public:};void Fun(A* a){    a->Test();}int main(int argc, char* argv[]){    B b;    Fun(&b);    return 0;}//虚表00401014   jmp         A::Test (00401140)

子类重写时的值

#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{public:    int x;    virtual void Test()    {        printf("A /n");    }};class B:public A{public:    virtual void Test()    {        printf("B /n");    }};void Fun(A* a){    a->Test();}int main(int argc, char* argv[]){    B b;    Fun(&b);    return 0;}//虚表@ILT+15(?Test@B@@UAEXXZ):00401014   jmp         BTest (004011e0)

析构函数问题

class A{private:    int* a;public:    A()    {        a = new int[10];    }    ~A()    {        delete a;    }    int* get_a()    {        return a;    }}class B:public A{private:    int* b;public:    B()    {        b = new int[5];    }    ~B()    {        delete b;    }    int* get_arr(int flag)    {        if(flag == 1 )        {            return b;        }        else        {            return get_a();        }    }}int main(){    A* p = new B;    delete p;    return 0;}

上述代码执行时,如果直接调用 指针类型是父类,那么只会执行父类的析构函数释放掉 int* a
而b类中的int* b却不会被释放掉

理论上最好的方式是 逐步往上调用所有的析构函数,这样才可以释放所有使用的内存

// _20180212.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include <stdio.h>#include <windows.h>class A{private:    int* a;public:    A()    {        a = new int[10];    }    virtual ~A()    {        delete a;        printf("析构 A /n");    }    int* get_a()    {        return a;    }};class B:public A{private:    int* b;public:    B()    {        b = new int[5];    }    ~B()    {        delete b;        printf("析构 B /n");    }    int* get_arr(int flag)    {        if(flag == 1 )        {            return b;        }        else        {            return get_a();        }    }};int main(int argc,char* argv[]){    A* p = new B;        delete p;    return 0;}

这里 将父类的析构函数定义为虚函数,那么delete的时候 就会调用子类重写父类虚析构函数的析构函数(虽然名字不相同,但是会自动重写_编译器约定)

并且此时析构函数现实从下往上逐步执行

Cpp7 C++的多态实现 -- 虚表

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2018年5月13日07:37:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Cpp7 C++的多态实现 -- 虚表http://cn-sec.com/archives/51575.html

发表评论

匿名网友 填写信息