空类实例化

昨天面试问到几个问题,当时感觉不是很了解,回来之后整理下,先说一下空类的实例化问题

一个C++空类实例化大小是多少?

一个C++空类实例化大小事实上并不为空,他有一个隐晦的1个byte.

首先:什么是类的实例化?

所谓类的实例化,就是在内存中分配一块地址。

例一:

#include<iostream>
using namespace std;

class A {};
class B {};
class C : public A {
    virtual void c_fun()=0;
};
class D : public B, public C {};

int main()
{
    cout<<"sizeof(A):"<<sizeof(A)<<endl;
    cout<<"sizeof(B):"<<sizeof(B)<<endl;
    cout<<"sizeof(C):"<<sizeof(C)<<endl;
    cout<<"sizeof(D):"<<sizeof(D)<<endl;
    return  0;
}

输出:

sizeof(A):1
sizeof(B):1
sizeof(C):4
sizeof(D):8
请按任意键继续. . .

为什么会出现这种结果呢?

类A和类B明明都是空类,它的大小应该为0,为什么编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加1个字节,这样空类在实例化之后再内存中得到了独一无二的地址,所以A,B的大小为为1。

类C是由类A派生而来,它里面有一个虚函数,由于有一个虚函数,就有一个指向虚函数表(虚表)的指针*_vptr,在32位的系统中分配给指针的大小为4个字节,所以最后得到C类的大小为4。

类D是由类B和类C派生而来的,它的大小应该为二者之和为5,为什么确是8呢?这是因为为了提高实例在内存中的存取效率,类的大小往往被调整到系统的整数倍,采取对长补齐,采用就近最近的整数倍数,就是该类的大小,所以D类的大小为8个字节。

当然,在不同的编译器上上述的结果可能不同。

例二:

#include<iostream>
using namespace std;

class A{
private: 
    int data;
};

class B{ 
private:
    int data;
    static int data1;
};
int B::data1=0;

void main()
{
    cout<<"sizeof(A) = "<< sizeof(A) <<endl;
    cout<<"sizeof(B) = "<< sizeof(B) <<endl;
}
输出:
sizeof(A) = 4
sizeof(B) = 4
请按任意键继续.

为什么类B多了一个数据成员,大小却和类A相同呢?

这是因为类B的静态成员数据被编译器放在程序的global data segment中,它是类的一个数据成员,但是它不影响类的大小,不管这个类实际产生了多少实例,还是派生了多少新的类,静态成员数据在类中永远只有一个实体存在,而类的非静态成员数据只有被实例化的时候,它们才存在。但是类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在,可以这么说,类的静态数据是一种特殊的全局变量。所以类A和类B的大小相同。

例三:

现在看一个有构造函数和析构类的大小:

#include<iostream>
using namespace std;

class A{
public :
    A(int a)
    {
        a=x;
    }
    void f(int x)
    {
        cout<<x<<endl;
    }
    ~A(){}

private:
    int x;
    int g;
};
class B{
public:
private:
    int data;
    int data2;
    static int xs;
};
int B::xs=0;

void main()
{
    A s(10);
    s.f(10);
    cout << "sizeof(a): "<< sizeof(A) << endl;
    cout << "sizeof(b): "<< sizeof(B) << endl;
}
输出:
10
sizeof(a): 8
sizeof(b): 8
请按任意键继续. . .

它们的结果均相同,可以看出类的大小与它当中的构造函数、析构函数,以及其他的成员函数无关,只与它当中的成员数据有关。

例四:

#include<iostream>
using namespace std;

class X {};
class Y : public virtual X {};
class Z : public virtual X {};
class A : public Y, public Z {};
// 上述为经典的钻石继承

int main()
{
    cout << "sizeof(X): " << sizeof(X) << endl;
    cout << "sizeof(Y): " << sizeof(Y) << endl;
    cout << "sizeof(Z): " << sizeof(Z) << endl;
    cout << "sizeof(A): " << sizeof(A) << endl;

    return 0;
}

输出:

sizeof(X): 1
sizeof(Y): 4
sizeof(Z): 4
sizeof(A): 8
请按任意键继续. .

和例一一样,类X是一个空类,但它事实上并不是空的,它有一个隐晦的1字节,那是被编译器安插进去的1个char,它主要的作用是使这个类的对象在内存中有一个独一无二的地址。

类Y和类Z的大小受三个因素的影响:

  1. 语言本身所造的额外的负担。因为类Y和类Z都是公有继承于类X,这里将会有vptr,在32位上为4字节;
  2. 编译器对特殊情况所作的优化处理。这里做了优化,一个空的虚基类被放在了派生类的最开头部分,也就是说它未花费任何额外的空间,这就节省了类X的1 byte;如果没做优化,一个空的虚基类会放在派生类的固定不变动的尾端;
  3. Alingment对长补齐的限制,对长补齐,上述未做优化,则虚基类X的1个字节与类Y的4个字节(共5个字节),补齐3个字节,最终为8个字节;如果做优化,则虚基类X的1个字节将被省略,只有类Y的4个字节,共4个字节,不需要补齐,最终为4个字节;          注意:补齐就是将数值调整为某个整数的整数倍,在32位机器上,一般Alignment 为 4 个字节。          
  4. 一个虚基类对象只会在派生类中存在以分实体,不管它在继承体系中出现多少次


以上几个例子总结类的大小:

  1. 类的非静态成员数据类型的大小之和
  2. 有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚表的指针)
  3. 为了优化存取效率,进行的边缘调整
  4. 与类中的构造函数/析构函数以及其他成员函数无关


参考文献:

http://www.voidcn.com/article/p-kezfjiov-om.html

posted @ 2018-11-06 10:10  OVS98  阅读(468)  评论(0编辑  收藏  举报