• 虚表的形成


    一切结论都必须以事实为依据,这样才能形成长久记忆!

    虚表的形成过程:

    一、对于非继承类而言:编译器会根据类中是否有虚函数产生虚表,如果有虚函数,则会形成虚表,虚表中会按照成员函数声明顺序存放函数的地址,从而形成存放函数入口地址的函数指针数组,最后把数组地址存放在类的开始的位置,只一个指针的大小。

    二、对于继承类而言:对于单继承,如果父类中有虚函数表,则编译器编译时,会把父类的虚表赋值一份,并把新的地址放在类的开始位置,只占一个指针大小的空间;对于多继承,编译器会把多个父类的虚表分别复制一份,并把新的虚表地址依次按照继承顺序存放在子类的开始位置。

    三、对于继承时,如果其重写其父类的虚函数(注意重写与重载的区别),编译器会把新的函数的地址更新到原来虚函数对应的位置,如果该类新增虚函数是,编译器会默认把新的虚函数的地址存放第一个虚表中,追加在其后。对于多重继承时,重写会覆盖所有继承虚表中被重写的虚函数地址;新增的虚函数会追加在第一个虚表的内虚表项的后边。

    四、虚函数就是指成员函数前加virtual 关键字的函数,但构造函数和友元函数(不是类成员函数)不会是虚函数,其他成员函数都可以使用virtual 修饰,当然纯虚函数也是虚函数。

    五、补充:在c++中纯虚函数与纯面向对象的语言,javaC#,中的接口在功能上是一致的,都是起到接口规范的作用,只是在C++中纯虚函数在类中定义,所以导致在C++,继承可以是多继承方式,而在纯面向对象中,只能是单继承。

    对于以上的结论的图像化的表示,图形方式的表示更直观一点,大家可以参考http://blog.csdn.net/haoel/article/details/1948051。

    但是关于这些论点的论证,希望大家参考我的代码。

    "朝花夕拾",为了论证之前结论,自己再次验证一下:

    注意:

    ①要确认当前的主机的位数以及编译器。64位机指针长度8byte,32位机指针长度为4byte.

    ②注意内存对齐机制,这个机制以为着,申明3byte内存的类大小:sizeof(类名) = 4.关于内存对齐和c语言结构体的对齐方式是一致的。

    ③空类的大小为1byte,原因是被编译器插进去的一个char ,使得这个class的不同实体(object)在内存中配置独一无二的地址,这样产生的对象的地址才不同。

    ④继承的时候,无论虚函数的的访问修饰符,和继承的方式如何都可以通过虚表进行调用。

    ⑤继承的时候,复制虚表本身,并没有复制虚表项中函数指针所指的函数的函数体,即虚函数在内存中的定义只有一份。

     

     以下是验证内容:

    结论一:

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 class A
     6 {
     7 
     8 };
     9 class B
    10 {
    11     virtual void  fun1()
    12     {
    13         cout <<"fun1" <<endl;
    14     }
    15     virtual void  fun2()
    16     {
    17         cout <<"fun2" <<endl;
    18     }
    19 };
    20 
    21 int main()
    22 {
    23     //空类大小为1
    24     cout << sizeof(A) <<endl;
    25     //指针大小为系统位数
    26     cout << sizeof(new A()) << endl;
    27     A t_a;
    28     //对象也是1
    29     cout << sizeof(t_a) << endl;
    30 
    31 
    32     cout << "******************" <<endl;
    33     
    34     //两个虚函数,放在一个虚函数表里,最后将虚表的入口地址存放在类开始位置。
    35     //指针大小为系统位数
    36     cout << sizeof(B) <<endl;
    37     //指针大小为系统位数
    38     cout << sizeof(new B()) << endl;
    39     B t_b;
    40     //指针大小为系统位数
    41     cout << sizeof(t_b) << endl;
    42 
    43     cout << "*******************" << endl;
    44     //指向对象的地址,指向对象的开始位置
    45     int *p = (int *)&t_b;
    46      //指向虚表的第一项
    47     int *p_fun =  (int *)*p;
    48     //指向虚表的第二项,
    49     //sizeof(void *)/sizeof(int) 而不使用1的原因是:
    50     //在不同位数的系统中,指针的加减的单位是取决于其指针的类型
    51     //如在32位系统中,int型指针+1,可以指向第二个指针。
    52     //当时在64位系统中,如果+1,此时指针移动的是sizeof(int)byte的位置,此时指向的不是第二个指针的位置,因为指针大小为8byte。所以使用sizeof(void *)/sizeof(int) 获得每个指针占用几个单位的int型空间。
    53     int *p_fun2 = p_fun + sizeof(void *)/sizeof(int);
    54     //将两项内的数据,即函数指针,强转。得到函数指针
    55     void (*f1)()  = (void (*) (void))*p_fun;
    56     void (*f2)()  = (void (*) (void))*p_fun2;
    57     //执行函数指针
    58     f1();
    59     f2();
    60     //打印的时候,需要转换成int型指针才能正常打印
    61     cout << (int *)f1 <<endl;
    62     cout << (int *)f2 <<endl;
    63     
    64 
    65 #if 0
    66     //这种方式,编译器报错
    67     cout << &(t_b.fun1) << endl;
    68     
    69 #endif
    70 
    71     return 0;
    72 }

    运行结果:

     

    结论二:

      1 #include <iostream>
      2 
      3 using namespace std;
      4 
      5 class A
      6 {
      7 public:
      8     virtual void a1()
      9     {
     10         cout << "a1" <<endl;
     11     }
     12     virtual void a2()
     13     {
     14         cout << "a2" << endl;
     15     }
     16 };
     17 class AA
     18 {
     19     
     20     virtual void aa1()
     21     {
     22         cout << "aa1" <<endl;
     23     }
     24     virtual void aa2()
     25     {
     26         cout << "aa2" << endl;
     27     }
     28 };
     29 class B:public A
     30 {
     31     
     32 };
     33 
     34 class C:public A,public AA
     35 {
     36     
     37 };
     38 
     39 int main()
     40 {
     41     cout << "*****************单继承验证**********************"<<endl;
     42     cout << sizeof(A) <<endl;
     43     cout << sizeof(B) <<endl;
     44 
     45     //证明①子类中的虚表与父类虚表入口地址不同
     46     A t_a;
     47     int * pa = (int *)&t_a;
     48     int *pa1 = (int *)*pa;
     49     cout << "父类虚表地址:" << pa1 <<endl;
     50 
     51     B t_b;
     52     int * pb = (int *)&t_b;
     53     int * pb1 = (int *)*pb;
     54     int * pb2 = pb1 + sizeof(void *)/sizeof(int);
     55     cout << "子类虚表地址:" << pb1 << endl;
     56     //结论①:结果地址不同。
     57 
     58     //证明②: 子类中仍然可以通过虚函数调用父类的方法,
     59     void (*f1)()  = (void (*) (void))*pb1;
     60     void (*f2)()  = (void (*) (void))*pb2;
     61     
     62     f1();
     63     f2();
     64     //结论②:子类可以通过新的虚表访问父类的虚函数
     65 
     66     //最终的结论:单继承的时候,是在内存中复制一份虚表,并将新的地址放在子类开始位置
     67     
     68     cout << "****************多继承验证*********************"  <<endl;
     69     //对于多继承,编译器会把多个父类的虚表分别复制一份,并把新的虚表地址依次按照继承顺序存放在子类的开始位置。
     70     AA t_aa;
     71     int *paa = (int *)&t_aa;
     72     int *paa1 = (int *)*paa;
     73 
     74     cout << "父类A的虚表地址:" << pa1 << endl;
     75     cout << "父类AA的虚表地址:" << paa1 << endl;
     76     
     77     C t_c;
     78     int *pc1 = (int *)&t_c;
     79     //继承A虚表的入口地址
     80     int *pc1_1 = (int *)*pc1;
     81     //继承AA虚表的入口地址
     82     int *pc2 = pc1 + sizeof(void *)/sizeof(int);
     83     int *pc2_1 = (int*)*pc2;
     84 
     85     cout << "子类中获得第一项虚表地址:" << pc1_1 << endl;
     86     cout << "子类中获得第二项虚表地址:" << pc2_1 << endl;
     87     
     88     //验证上述地址确实是copy后的新的虚表地址
     89     cout << "通过继承子类A的虚表执行虚函数" <<endl;
     90     int *pc1_2 = pc1_1 + sizeof(void *)/sizeof(int);
     91     void (*fc1_1)()  = (void (*) (void))*pc1_1;
     92     void (*fc1_2)()  = (void (*) (void))*pc1_2;
     93 
     94     fc1_1();
     95     fc1_2();
     96     
     97     
     98     cout << "通过继承子类AA的虚表执行虚函数" <<endl;
     99     int *pc2_2 = pc2_1 + sizeof(void *)/sizeof(int);
    100     void (*fc2_1)()  = (void (*) (void))*pc2_1;
    101     void (*fc2_2)()  = (void (*) (void))*pc2_2;
    102 
    103     fc2_1();
    104     fc2_2();
    105 
    106     return 0;
    107 }  

     运行截图:

    结论三:

      1 #include <iostream>
      2 
      3 using namespace std;
      4 
      5 class A
      6 {
      7     public:
      8         virtual void a1()
      9         {
     10             cout << "a1" <<endl;
     11         }
     12         virtual void a2()
     13         {
     14             cout << "a2" << endl;
     15         }
     16 };
     17 class AA
     18 {
     19 
     20     virtual void a1()
     21     {   
     22         cout << "aa1" <<endl;
     23     }   
     24     virtual void aa2()
     25     {   
     26         cout << "aa2" << endl;
     27     }   
     28 };
     29 class B:public A
     30 {
     31 
     32 };
     33 
     34 class BB:public A
     35 {
     36     //重写A的虚函数,注意不是重载,重载不是一个函数,只是函数名相同罢了
     37     virtual void a1()
     38     {
     39         cout << "bb1" <<endl;
     40     }
     41     virtual void a2()
     42     {
     43         cout << "bb2" << endl;
     44     }
     45     //新增虚函数的时候,会将新增的虚函数地址作为新的表项,追加在原虚表的后边
     46     virtual void b3()
     47     {
     48         cout << "bb3" << endl;
     49     }
     50 };
     51 
     52 class C:public A,public AA
     53 {
     54     //重写A的虚函数,注意不是重载,重载不是一个函数,只是函数名相同罢了
     55     virtual void a1()
     56     {
     57         cout << "cc1" <<endl;
     58     }
     59     virtual void a2()
     60     {
     61         cout << "cc2" << endl;
     62     }
     63     //新增虚函数的时候,会将新增的虚函数地址作为新的表项,追加在原虚表的后边
     64     virtual void b3()
     65     {
     66         cout << "cc3" << endl;
     67     }
     68 };
     69 
     70 
     71 int main()
     72 {
     73     A t_a;
     74     int * pa = (int *)&t_a;
     75     int *pa1 = (int *)*pa;
     76     //cout << "父类虚表地址:" << pa1 <<endl;
     77 
     78     B t_b;
     79     int * pb = (int *)&t_b;
     80     int * pb1 = (int *)*pb;
     81     int * pb2 = pb1 + sizeof(void *)/sizeof(int);
     82     //cout << "子类虚表地址:" << pb1 << endl;
     83 
     84     void (*f1)()  = (void (*) (void))*pb1;
     85     void (*f2)()  = (void (*) (void))*pb2;
     86 
     87     f1();
     88     f2();
     89     //结论②:子类可以通过新的虚表访问父类的虚函数
     90 
     91 
     92     BB t_bb;
     93     int * pbb = (int *)&t_bb;
     94     int * pbb1 = (int *)*pbb;
     95     int * pbb2 = pbb1 + sizeof(void *)/sizeof(int);
     96     int * pbb3 = pbb2 + sizeof(void *)/sizeof(int);
     97     //cout << "子类虚表地址:" << pb1 << endl;
     98 
     99     void (*fb1)()  = (void (*) (void))*pbb1;
    100     void (*fb2)()  = (void (*) (void))*pbb2;
    101     void (*fb3)()  = (void (*) (void))*pbb3;
    102     cout << "***********************单继承的情况****************************" << endl;
    103 
    104     cout  << "验证重写的时候,覆盖原虚表中被重写的虚函数的入口地址" << endl;
    105     fb1();
    106     fb2();
    107     cout  << "验证新增虚函数的时候,追加在虚表已有表项的后边" << endl;
    108     fb3();
    109 
    110     
    111     cout << "***********************多继承的情况****************************" << endl;
    112     //验证多重继承的时候,重写的虚函数地址会覆盖所有虚表中中被重写的虚函数地址,
    113     //验证多重继承的时候,新增虚函数会自动追加到第一个继承虚表的后边。不会再追加到继承的其他虚表的虚表项。
    114     
    115     C t_c;
    116     //虚表1    
    117     int * pc1 = (int *)&t_c;
    118     //虚表1的表项1
    119     int * pc1_1 = (int *)*pc1;
    120     //虚表1的表项2
    121     int * pc1_2 = pc1_1 + sizeof(void *)/sizeof(int);
    122     //虚表1的表项3
    123     int * pc1_3 = pc1_2 + sizeof(void *)/sizeof(int);
    124     
    125     //虚表2
    126     int * pc2 = pc1 + sizeof(void *)/sizeof(int);
    127     //虚表2的表项1
    128     int * pc2_1 = (int *)*pc2;
    129     //虚表2的表项2
    130     int * pc2_2 = pc2_1 + sizeof(void *)/sizeof(int);
    131     //虚表2的表项3
    132     int * pc2_3 = pc2_2 + sizeof(void *)/sizeof(int);
    133 
    134     //重写A的虚函数1
    135     void (*fc1_1)()  = (void (*) (void))*pc1_1;
    136     //重写A的虚函数2
    137     void (*fc1_2)()  = (void (*) (void))*pc1_2;
    138     //追加新的虚函数3
    139     void (*fc1_3)()  = (void (*) (void))*pc1_3;
    140     
    141     
    142     //重写AA的虚函数1,因为A和AA的第一个虚函数是相同的
    143     void (*fc2_1)()  = (void (*) (void))*pc2_1;
    144     //继承AA的虚函数2
    145     void (*fc2_2)()  = (void (*) (void))*pc2_2;
    146     //追加新的虚函数3,段错误,只追加到第一个虚表的后边,当前是虚表2,没有追加,如果访问就段错误
    147     void (*fc2_3)()  = (void (*) (void))*pc2_3;
    148     
    149     cout << "重写A的虚函数1" << endl;
    150     fc1_1();
    151     cout << "重写A的虚函数2" << endl;
    152     fc1_2();
    153     cout << "追加虚表1的虚函数" << endl;
    154     fc1_3();
    155 
    156 
    157     cout << "重写AA的虚函数1" << endl;
    158     fc2_1();
    159     cout << "继承AA的虚函数2" << endl;
    160     fc2_2();
    161     //cout << "追加虚表1的虚函数" << endl;
    162     //fc2_3();
    163 
    164     return 0;
    165 }

     运行截图:

  • 相关阅读:
    c# 正则表达式 首字母转大写
    c# WebBrowser获取cookie
    c# 求最小公倍数
    Response.Redirect与Server.Transfer区别-转
    asp 读文件 比较ip
    asp数组的使用
    如何解决#1045
    mysql limit分页查询效率
    Docker 容器管理:rancher
    Docker监控:google/cadvisor
  • 原文地址:https://www.cnblogs.com/cz-blog/p/4264277.html
Copyright © 2020-2023  润新知