关于虚拟继承类的大小问题探索,VC++ 和 G++ 结果是有区别的

简介: 昨天笔试遇到个 关于类占用的空间大小的问题,以前没怎么重视,回来做个试验,还真发现了问题,以后各位笔试考官门,出题时请注明是用什么编译器。        vc6/vc8 cl 和 Dev-C 的g++ 来做的测试: 上代码, 测试代码: #include class A{public: in...

昨天笔试遇到个 关于类占用的空间大小的问题,以前没怎么重视,回来做个试验,还真发现了问题,以后各位笔试考官门,出题时请注明是用什么编译器。 

      vc6/vc8 cl 和 Dev-C 的g++ 来做的测试:

上代码,

测试代码:

#include <stdio.h>
class A
{
public:
 int x;
  int y;
 A()
 {
  x = 1;
     y = 2;
 };
 void go()
 {
  printf("A go()\n");
 }
    virtual void move() 
 {
  printf ("A Move()\n");
 }
};

class  B: virtual public A
{
public:
 int x;
 int y;
 B()
 {
  x = 3;
  y = 4;
 }
 void go()
 {
  printf("B go()\n");
 }
 virtual void move()
 {
  printf ("B Move()\n");
 }
 virtual void move2()
 {
  printf ("B Move2()\n");
 }
};

class C: public A
{
public:
 int x;
 int y;
 C()
 {
  x = 5;
  y = 6;
 }
 /*
 virtual void move()
 {
  printf ("C Move()\n");
 }
 virtual void move3()
 {
  printf ("C Move2()\n");
 }
 */
};

class D:virtual public C,virtual public B
{

};

void PrintClass(void * Base,int size)
{
 //打印结构体系
 DWORD  p, pNext;
 printf("--------size= %d------------------\n",size);
 for(int i = 0;i<size/4;i++)
 {
        p = (DWORD)Base+i*4;
        pNext = *(DWORD *)(p);
  printf("   |--0x%08x    :    0x%08x ",p,pNext);  //打印类里都是些什么东西
  if(pNext>0x400000)  //打印一下此地址下的连续三个地址的内容
  {
   printf("  :0x%08x 0x%08x 0x%08x",*(DWORD *)pNext,*(DWORD *)(pNext+4),*(DWORD *)(pNext+8));
  }
  printf("\n");
 }
    printf("   |--End \n\n");
}

int main(int argc, char* argv[])
{
 void * p1,*p2,*p3,*p4;
 D dd;   //因为它继承了所有,所以用它做测试
 B * boy = &dd;   //都是父类
 C * child = &dd;

 A * Man = boy;

 int sizeA = sizeof(A);
 int sizeB = sizeof(B);
 int sizeC = sizeof(C);
 int sizeD = sizeof(D);

 

//转化成指针

 p1 = (void *)Man;
 p2 = (void *)boy;
 p3 = (void *)child;
 p4 = (void *)&dd;

 

   PrintClass(p1,sizeA);
   PrintClass(p2,sizeB);
   PrintClass(p3,sizeC);
   PrintClass(p4,sizeD);

   
   system("pause");

 return 0;
}

 

上VC++ 的结果 VC6 和 VS2005 是一样的 上一张图  A B C D 大小依次是 12 32 20 56

再上一张 GCC的结果图  A B C D的大小 依次为 12 24 20 48

A的虚函数表中存放的是A所有的虚函数的地址,在虚函数的地址后面存放的是距离虚基类的偏移量。A没有虚基类,所以后面存放的是0

B的虚基类表中存放的B所有的虚函数的地址,虚函数地址后面存放的是距离每个虚基类的偏移量。例如B有一个虚基类A,且B与A的偏移量是12,即结果中的0xfffffff4(-12)

C不是虚继承没有虚基类表,且C与A共享虚函数表,所以只存在虚函数表,里面存放的虚函数的地址。

D存在一个虚基类,所以存在虚基类表,由于D没有虚函数,所以虚基类表中存放的只是相对虚基类的偏移量。然后存放的是虚基类C以及C共享A的虚函数表,最后是虚基类B以及B的虚基类A的分布。

修改后的测试代码:

#include <stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

#define DWORD unsigned long
class A
    {
    public:
        int x;
        int y;
        A()
            {
            x = 1;
            y = 2;
            };
        void go()
            {
            printf("A go()\n");
            }
        virtual void move()
            {
            printf ("A Move()\n");
            }
    };

class  B: virtual public A
    {
    public:
        int x;
        int y;
        B()
            {
            x = 3;
            y = 4;
            }
        void go()
            {
            printf("B go()\n");
            }
        virtual void move()
            {
            printf ("B Move()\n");
            }
        virtual void move2()
            {
            printf ("B Move2()\n");
            }
    };

class C: public A
    {
    public:
        int x;
        int y;
        C()
            {
            x = 5;
            y = 6;
            }
        /*
        virtual void move()
        {
         printf ("C Move()\n");
        }
        virtual void move3()
        {
         printf ("C Move2()\n");
        }
        */
    };

class D:virtual public C,virtual public B
    {

    };

void PrintClass(void * Base,int size)
    {
    //打印结构体系
    DWORD  p, pNext;
    printf("--------size= %d------------------\n",size);
    for(int i = 0; i<size/4; i++)
            {
            p = (DWORD)Base+i*4;
            pNext = *(DWORD *)(p);
            printf("   |--0x%08x    :    0x%08x ",p,pNext);  //打印类里都是些什么东西
            if(pNext>0x400000)  //打印一下此地址下的连续三个地址的内容
                    {
                    printf("  :0x%08x 0x%08x 0x%08x",*(DWORD *)pNext,*(DWORD *)(pNext+4),*(DWORD *)(pNext+8));
                    }
            printf("\n");
            }
    printf("   |--End \n\n");
    }

int main(int argc, char* argv[])
    {
    void * p1,*p2,*p3,*p4;
    D dd;   //因为它继承了所有,所以用它做测试
    B * boy = &dd;   //都是父类
    C * child = &dd;

    A * Man = boy;

    int sizeA = sizeof(A);
    int sizeB = sizeof(B);
    int sizeC = sizeof(C);
    int sizeD = sizeof(D);
    //转化成指针

    p1 = (void *)Man;
    p2 = (void *)boy;
    p3 = (void *)child;
    p4 = (void *)&dd;


    typedef void (*VFun)();
    B aa;
    int **vPtr=(int**)&aa;
    cout<<"B first virtual table address : "<<vPtr[0]<<endl;
    cout<<"B::move address : "<<(int*)vPtr[0][0]<<endl;
    VFun pVF=VFun(vPtr[0][0]);
    pVF();
    cout<<"B move2 address : "<<(int*)vPtr[0][1]<<endl;
    pVF=VFun(vPtr[0][1]);
    pVF();
    cout<<"B end address : "<<(int*)vPtr[0][2]<<endl;
    cout<<vPtr[0][2]<<endl;
    cout<<"B member1 "<<vPtr[1]<<endl;
    cout<<"B member2 "<<vPtr[2]<<endl;
     cout<<"B second virtual table address : "<<vPtr[3]<<endl;
    cout<<"A move address : "<<(int*)vPtr[3][0]<<endl;
    pVF=VFun(vPtr[3][0]);
    pVF();
    cout<<"A end address : "<<(int*)vPtr[3][1]<<endl;
    cout<<vPtr[3][1]<<endl;

    C cc;
    vPtr=(int**)&cc;
    cout<<"C first virtual table address : "<<vPtr[0]<<endl;
    cout<<"A::move address : "<<(int*)vPtr[0][0]<<endl;
    pVF=VFun(vPtr[0][0]);
    pVF();
    cout<<"A end address : "<<(int*)vPtr[0][1]<<endl;
    cout<<vPtr[0][1]<<endl;

    vPtr=(int**)&dd;
    cout<<"D first virtual table address : "<<vPtr[0]<<endl;
    cout<<"D end address : "<<(int*)vPtr[0][0]<<endl;
    cout<<vPtr[0][0]<<endl;
    cout<<"D end address : "<<(int*)vPtr[0][1]<<endl;
    cout<<vPtr[0][1]<<endl;

    cout<<"D second virtual table address : "<<vPtr[1]<<endl;
    cout<<"C::move address : "<<(int*)vPtr[1][0]<<endl;
    pVF=VFun(vPtr[1][0]);
    pVF();
    cout<<"C end address : "<<(int*)vPtr[1][1]<<endl;
    cout<<vPtr[1][1]<<endl;

    cout<<"D member1 "<<vPtr[2]<<endl;
    cout<<"D member2 "<<vPtr[3]<<endl;
    cout<<"D member3 "<<vPtr[4]<<endl;
    cout<<"D member4 "<<vPtr[5]<<endl;

    cout<<"D third virtual table address : "<<vPtr[6]<<endl;
    cout<<"B::move address : "<<(int*)vPtr[6][0]<<endl;
    pVF=VFun(vPtr[6][0]);
    pVF();
    cout<<"B::move address : "<<(int*)vPtr[6][1]<<endl;
    pVF=VFun(vPtr[6][1]);
    pVF();
    cout<<"B end address : "<<(int*)vPtr[6][2]<<endl;
    cout<<vPtr[6][2]<<endl;

    cout<<"B member1 "<<vPtr[7]<<endl;
    cout<<"B member2 "<<vPtr[8]<<endl;

    cout<<"D four virtual table address : "<<vPtr[9]<<endl;
    cout<<"A::move address : "<<(int*)vPtr[9][0]<<endl;
    pVF=VFun(vPtr[9][0]);
    pVF();
    cout<<"A end address : "<<(int*)vPtr[9][1]<<endl;
    cout<<vPtr[9][1]<<endl;

    cout<<"A member1 "<<vPtr[10]<<endl;
    cout<<"A member2 "<<vPtr[11]<<endl;


    PrintClass(p1,sizeA);
    PrintClass(p2,sizeB);
    PrintClass(p3,sizeC);
    PrintClass(p4,sizeD);

    system("pause");

    return 0;
    }
View Code

运行结果:

[root@localhost Inside the C++ object model]# ./vcGcc 
B first virtual table address : 0x8049bcc
B::move address : 0x80496cc
B Move()
B move2 address : 0x80496f0
B Move2()
B end address : 0xfffffff4
-12
B member1 0x3
B member2 0x4
B second virtual table address : 0x8049be0
A move address : 0x80496e0
B Move()
A end address : 0
0
C first virtual table address : 0x8049bb0
A::move address : 0x8049636
A Move()
A end address : 0
0
D first virtual table address : 0x8049b14
D end address : 0
0
D end address : 0xfffffffc
-4
D second virtual table address : 0x8049b20
C::move address : 0x8049636
A Move()
C end address : 0
0
D member1 0x1
D member2 0x2
D member3 0x5
D member4 0x6
D third virtual table address : 0x8049b38
B::move address : 0x80496cc
B Move()
B::move address : 0x80496f0
B Move2()
B end address : 0xfffffff4
-12
B member1 0x3
B member2 0x4
D four virtual table address : 0x8049b4c
A::move address : 0x80496e0
B Move()
A end address : 0x8049b14
134519572
A member1 0x1
A member2 0x2
--------size= 12------------------
   |--0xbfab7034    :    0x08049b4c   :0x080496e0 0x08049b14 0x08049b20
   |--0xbfab7038    :    0x00000001 
   |--0xbfab703c    :    0x00000002 
   |--End 

--------size= 24------------------
   |--0xbfab7028    :    0x08049b38   :0x080496cc 0x080496f0 0xfffffff4
   |--0xbfab702c    :    0x00000003 
   |--0xbfab7030    :    0x00000004 
   |--0xbfab7034    :    0x08049b4c   :0x080496e0 0x08049b14 0x08049b20
   |--0xbfab7038    :    0x00000001 
   |--0xbfab703c    :    0x00000002 
   |--End 

--------size= 20------------------
   |--0xbfab7014    :    0x08049b20   :0x08049636 0x00000000 0x00000000
   |--0xbfab7018    :    0x00000001 
   |--0xbfab701c    :    0x00000002 
   |--0xbfab7020    :    0x00000005 
   |--0xbfab7024    :    0x00000006 
   |--End 

--------size= 48------------------
   |--0xbfab7010    :    0x08049b14   :0x00000000 0xfffffffc 0x08049c00
   |--0xbfab7014    :    0x08049b20   :0x08049636 0x00000000 0x00000000
   |--0xbfab7018    :    0x00000001 
   |--0xbfab701c    :    0x00000002 
   |--0xbfab7020    :    0x00000005 
   |--0xbfab7024    :    0x00000006 
   |--0xbfab7028    :    0x08049b38   :0x080496cc 0x080496f0 0xfffffff4
   |--0xbfab702c    :    0x00000003 
   |--0xbfab7030    :    0x00000004 
   |--0xbfab7034    :    0x08049b4c   :0x080496e0 0x08049b14 0x08049b20
   |--0xbfab7038    :    0x00000001 
   |--0xbfab703c    :    0x00000002 
   |--End 

 

对比 VC++ 和 G++  的结果 我们会发现

 

VC++ 大小依次为 : 12 32 20 56

G++ 大小依次为:    12 24 20 48

代码都是一样的 但是得到的结果 却不一样。

GCC 里的C++ 是完全遵循C++标准的  VC++的 好像不是那么血统纯正,结果出乎意料之外

 

分析:

先看GCC的图

对比看 A  和  C , 

A是基类 C是子类 直接继承的 

A 里面有虚函数 那么一定有一个虚函数表指针 vfptr  大小为 4字节  0x00404420 就是虚函数表地址 

再加上两个整型变量 4 + 4  一共大小 是 12  所以 size(A) = 12 ;

C从A那里继承 所以子成员都被继承下来了 4 + 4 再加上虚函数表指针(vfptr) + 4  然后 加上 自己的两个成员变量 4 + 4

一共是 4+4+4+4+4 = 20  ;

对比 A 和 B,

B 是虚拟继承A的 也就是会有一个虚基类表指针(vbptr) 0x40440C 就是地址

A = 12 

B = 8(两变量)+4(vbptr)+A = 8 + 4 + 12 = 24 ;

我们也很容易明白

 

对比 A , B , C 和  D

当你看到D的结构时,我们很容易看到 D 是这么 个结构  D = [vbptr] + [C] +[B] 的

因为 D 从 B C那里虚拟继承下来 所以继承了他们已有的变量和虚函数表 

所以 D = 4 + C + B = 4 +20 + 24 = 48

好了G++的分析完了 ,如果觉得有错误,请指正。

按照这个道理来讲,是完全没问题的,我们计算一个类的大小就可以如此。

从上面图中我们可以看到 他们的虚函数表和虚基类表指针都会被当做共享继承下来,不知道我说的对不对,希望能得到高人指点

 

再来看看 VC++

 VC++就会让人很蛋疼 毕竟它出来的是 C++标准好像它还不兼容,完全是微软的那一套吧(自己猜测)

 我们看看 VC++出来的 结果  12 32 20 56  对比 G++的 B 和 D的 不一样

那就仔细看看 B 和 D 发现了点怪问题

大家可以自己看看 发现问题没有 可能我这种方式不太对 但是 对比D和 C 我们也能发现点 什么

D= 4+ B + C 的 也就是说  它虚拟继承下来 但是 发现其中有一个地址 竟然里面是 0x00000000, 不知道是什么原因,

有兴趣可以分析下原因。 虽然最终实现的效果都是一样的。

 

总结: 看来 GCC 要纯正些 跟书上讲的一样的 但是VC++ 好像有点问题,并不是书上讲的那样,以后考官出题还是要以GCC为标准啊,VC++是个半调子啊

最后 我还在测试里 加入了 测试代码

   Man->move();
   boy->move();
   child->move();

VC++的输出结果是 :

B Move()
B Move()
C Move()

 

GCC输出的结果是:

B Move()
B Move()
C Move()

输出是一样的 说明他们达到的效果是一样的,但是数据结构却有着差别

有兴趣的同学可以研究下,这里也提醒下 各大公司的考官们,出题的时候 要提示是什么编译器 或者是  C++标准 。

 

C++虚继承内存对象模型探讨

最近看了下Inside C++里面讲的对虚继承层次的对象的内存布局,发现在不同编译器实现有所区别。因此,自己动手探索了一下。结果如下:

首先,说说GCC的编译器.

它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针

class A {

int a;

virtual ~A(){}

};

class B:virtual public A{

virtual ~B(){}

virtual void myfunB(){}

};

class C:virtual public A{

virtual ~C(){}

virtual void myfunC(){}

};

class D:public B,public C{

virtual ~D(){}

virtual void myfunD(){}

};

以上代码中 sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.

解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针

如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16. 注意此时虽然D的大小和虚继承一样,但是内存布局却不同。

然后,来看看VC的编译器

vc对虚表指针的处理比GCC复杂,它根据是否为虚继承来判断是否在继承关系中共享虚表指针,而对指向虚基类的指针和GCC一样是不共享,当然也不可能共享

代码同上。

运行结果将会是sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.

解释:A中依然是int+虚表指针。B,C中由于是虚继承因此虚表指针不共享,由于B,C加入了自己的虚函数,所以B,C分别自己维护一个虚表指针,它指向自己的虚函数。(注意:只有子类有新的虚函数时,编译器才会在子类中添加虚表指针)因此B,C大小为A+自己的虚表指针+指向虚基类的指针。D由于B,C都是虚继承,因此D只包含一个A的副本,同时D是从B,C普通继承的,而不是虚继承的,因此没有自己的虚表指针。于是D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针

同样,如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚表指针的

相关文章
|
22天前
|
存储 C++ 容器
C++入门指南:string类文档详细解析(非常经典,建议收藏)
C++入门指南:string类文档详细解析(非常经典,建议收藏)
31 0
|
22天前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
31 0
存储 编译器 Linux
15 0
|
2天前
|
编译器 C++
标准库中的string类(上)——“C++”
标准库中的string类(上)——“C++”
|
2天前
|
编译器 C++
自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”
自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”
|
2天前
|
存储 编译器 C++
自从学了C++之后,小雅兰就有对象了!!!(类与对象)(上)——“C++”
自从学了C++之后,小雅兰就有对象了!!!(类与对象)(上)——“C++”
|
3天前
|
C++
【C++成长记】C++入门 | 类和对象(下) |Static成员、 友元
【C++成长记】C++入门 | 类和对象(下) |Static成员、 友元
|
3天前
|
存储 编译器 C++
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
|
7天前
|
编译器 C语言 C++
【C++初阶(九)】C++模版(初阶)----函数模版与类模版
【C++初阶(九)】C++模版(初阶)----函数模版与类模版
11 0
|
9天前
|
存储 编译器 C语言
C++类与对象
C++类与对象
2 0