【C++】类大小的计算

1 初识

测试环境:

  • 操作系统:Windows 10
  • 编译器:g++.exe (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 8.1.0

本机环境测试时,指针大小为 8 字节,请读者根据实际情况自行判断

1.1 内存对齐

可以使用 sizeof 或者 alignof 判断基本类型的对齐大小。但面对自定义类型的时候,前者就不行了,而后者是专门用来求对齐大小的,所以更推荐后者。

对齐基本规则

  • 对于每个数据成员,第一个成员位于偏移量为 0 的位置,每个数据成员的偏移量必须为自身对齐大小也即 alignof() 的倍数
  • 最后类的总大小,需要为类中最大的对齐大小的倍数(此大小也为该类的对齐大小)

先来看两个简单的例子:

class Base1 { //12
    char ch1;
    int d;
    char ch2;
};

【C++】类大小的计算_第1张图片

由图可知,Base1 的最终大小为:12 字节

  • sizeof(Base1) == 12
  • alignof(Base1) == 4
class Base2 { //8
    int d;
    char ch1;
    char ch2;
};

【C++】类大小的计算_第2张图片

同样的计算得到最终大小为:8 字节

  • sizeof(Base2) == 8
  • alignof(Base2) == 4

1.2 空类

class EmptyClass { }; //1
class HaveEmptyClass { //2
    EmptyClass a;
    EmptyClass b;
};

运行验证得

  • sizeof(EmptyClass) == 1, alignof(EmptyClass) == 1
  • sizeof(HaveEmptyClass) == 2, alignof(EmptyClass) == 1

至于为什么空类大小为 1,首先空类可以被实例化,实例化之后在内存中需要一个地址,如此一来需要占用空间,因此往往会给空类偷偷地加一个字节。

1.3 类内的静态变量与普通方法

直接上结论吧

  • 静态变量(static 修饰的变量)不纳入类的空间大小计算
  • 普通成员方法不纳入类的空间大小计算
class Test { //1
    static int a;
    void Func();
};

这个类的大小还是 1,相当于空类的情况

1.4 类内存在其他类的数据成员

class Base { //12, alignof(Base) == 4
    char ch1;
    int d;
    char ch2;
};

class HaveBaseClass { //20
    char ch1;
    Base b;
    char ch2;
};

Base 的对齐大小为 4,可用 alignof(Base) 求出,事实上你也可以自己看出来(该类中数据成员中最大对齐大小即为该类的对齐大小)

  • sizeof(HaveBaseClass) == 20
  • alignof(HaveBaseClass) == 4

2 进阶

如果你没完全理解前面的内容,那就慢慢想,想明白了再看下面的

2.1 继承的类大小

继承来数据成员顺序是怎么安排的,我们写个代码做测试

class Base {
    double d;
    char ch1;
    char ch2;
};

class Derived : public Base {
    char ch3;
};

Derived 的大小:如果 Base 中的数据成员在前,得到的大小应该为 16;如果是 Derived 自己的在前,得到的大小应该为 24

结果:计算的到 Derived 大小为 16

结论:父类数据成员在前,子类数据成员在后

2.2 虚函数与纯虚函数

前面提到了普通成员不纳入空间计算,但事实上只有虚函数(包括纯虚函数)才会纳入计算。也就是说,什么静态方法、常成员函数也不纳入空间计算。

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量);当然如果出现多个虚函数时,仍然只有一个指向虚表的指针

class Base { //16
    char ch1;
    virtual void Fun1();
    virtual void Fun2();
    char ch2;
};

上面 Base 的大小为 16,应证了虚函数表的指针存在于对象实例中最前面的位置的说法。

详细情况请见这一篇文章:C++ 虚函数表,扒过来一张图,还有多重继承和虚函数是否覆盖的情况,建议读者从它的文章中理解清楚。

【C++】类大小的计算_第3张图片

2.3 #pragma pack(n) 设定对齐方式

数据对齐规则

  • 如果 n >= 该变量所占用的字节数,那么对齐按原来的来;反之对齐为 n 的倍数
  • 如果 n 大于所有成员变量类型所占用的字节数,那么结构体的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为 n 的倍数

总结:两者相比,取小的为对齐标准

#pragma pack(2)
class Base {
    char ch1;
    int d;
    char ch2;
};
#pragma pack()

结果:sizeof(Base) == 8,alignof(Base) == 2


参考

[1] C++ 类的大小计算 https://blog.csdn.net/fengxinlinux/article/details/72836199

[2] C++ 虚函数表 https://www.jianshu.com/p/7bda11cf4a58

你可能感兴趣的:(C++)