C++类的内存分配

本文详细探讨了C++类的内存分配,包括内存对齐的原理和规则,成员变量的存储方式,static关键字的影响,#pragma pack字节对齐的用法,虚函数和虚基类的作用。通过实例测试展示了不同情况下类占用内存的情况,揭示了编译器如何处理内存对齐和数据结构布局。
摘要由CSDN通过智能技术生成

内存对齐

内存对齐的原因

  1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

  1. 数据成员对齐规则: 类(class)、结构体(struct)或联合体(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
  2. 整体对齐规则: 在数据成员完成各自对齐之后,类、结构体或联合体本身也要进行对齐,对齐将按照#pragma pack指定的数值和类、结构体或联合体最大数据成员长度中,比较小的那个进行。

结合1、2可推断,当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

成员变量

测试一

测试代码:

#include <iostream>
using namespace std;

class A {
};

class B {
public:
    short a;
};

class C {
public:
    short a;
    int b;
};

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

测试结果:

A:1
B:2
C:8

当为空类时,编译器会给空类分配1个字节的内存空间,这样的话,创建的实例所指向的就是有意义的内存空间。

对比类B和C,B的成员变量为short型,占2个字节,C的第一个成员变量存放在offset为0的地方,第二个成员变量占4字节,故编译器插入2个字节凑足4的整数倍,使得第二个成员变量存放在offset为4的地方。

测试二

测试代码:

#include <iostream>
using namespace std;

class A1{};
class A2{};

class B1:public A1{
public:
    A1 a1;
    char ca;
};

class B2:public A1{
public:
    A2 a2;
    char ca;
};

class B3:public A1{
public:
    char ca;
    A1 a1;
};

int main() {
    cout<<sizeof(B1)<<endl;
    cout<<sizeof(B2)<<endl;
    cout<<sizeof(B3)<<endl;
    return 0;
}

测试结果:

3
2
2

如果基类没有成员,标准允许派生类的第一个成员与基类共享地址,基类并没有占据任何实际的空间,但此时若该派生类的第一个成员类型仍然是基类,编译器仍会为基类分配1字节的空间,这是因为C++标准要求类型相同的对象必须地址不同。

测试三

测试代码:

#include <iostream>
using namespace std;

class A {
public:
    short a;
    int b;
    double c;
};

class B {
public:
    short a;
    double b;
    int c;
};

int main() {
    A classA;
    B classB;

    cout << "classA:" << sizeof(classA) << endl;
    cout << "classA.a:" << &classA.a << endl;
    cout << "classA.b:" << &classA.b << endl;
    cout << "classA.c:" << &classA.c << endl;

    cout << "classB:" << sizeof(classB) << endl;
    cout << "classB.a:" << &classB.a << endl;
    cout << "classB.b:" << &classB.b << endl;
    cout << "classB.c:" << &classB.c << endl;
    return 0;
}

测试结果:

classA:16
classA.a:0x28ff20
classA.b:0x28ff24
classA.c:0x28ff28
classB:24
classB.a:0x28ff08
classB.b:0x28ff10
classB.c:0x28ff18

类A的第一个成员变量存放在0x28ff20~0x28ff21,第二个成员变量占4字节,offset为4,在0x28ff24~0x28ff27,第三个成员变量占8字节,offset为8,在0x28ff28~0x28ff2f,故类A占16字节。

类B的第一个成员变量存放在0x28ff08~0x28ff09,第二个成员变量占8字节,offset为8,在0x28ff10~0x28ff17,第三个成员变量占4字节,offset取4的整数倍为16,在0x28ff18~0x28ff1b,由于类B本身也要对齐,这里最大成员变量长度为8,故类B的大小需为8的整数倍,编译器在最后插入4个字节,共占24字节。

static关键字

测试代码:

#include <iostream>
using namespace std;

class A {
public:
    static short a;
    int b;
    double c;
};
short A::a = 0;

int main() {
    A classA;

    cout << "A:" << sizeof(A) << endl;
    cout << "A::a:" << &A::a << endl;
    cout << "classA:" << sizeof(classA) << endl;
    cout << "classA.a:" << &classA.a << endl;
    cout << "classA.b:" << &classA.b << endl;
    cout << "classA.c:" << &classA.c << endl;
    return 0;
}

测试结果:

A:16
A::a:0x407020
classA:16
classA.a:0x407020
classA.b:0x28ff20
classA.c:0x28ff28

类A为16字节,这是由于在该测试代码中,其静态成员变量存储在全局数据区,独立于类的实例,为所有该类实例所共享。

#pragma pack字节对齐

测试代码:

#include <iostream>
using namespace std;

#pragma pack(1)
class A {
public:
    char a;
    short b;
    double c;
};
#pragma pack()

#pragma pack(4)
class B1 {
public:
    char a;
    short b;
    double c;
};
#pragma pack()

#pragma pack(4)
class B2 {
<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值