C++中的内存对齐

内存对齐:原理、原因与编程实践,
本文介绍了内存对齐的概念,解释了为何需要内存对齐,以及它如何影响CPU的内存访问效率。讨论了结构体的对齐规则和指定对齐的方法,包括使用#pragmapack。最后强调了内存对齐在程序移植和性能优化中的作用。

参考:力扣内存对齐

什么是内存对齐

在访问特定类型变量的时候通常在特定的内存地址访问,这就需要对这些数据在内存中存放的位置进行限制,各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。编译器将程序中的每个 数据单元 的地址安排在机器字的整数倍的地址指向的内存之中。

为什么需要内存对齐?

主要是由于 CPU 的访问内存的特性决定,CPU 访问内存时并不是以字节为单位来读取内存,而是以机器字长为单位,实际机器字长由 CPU 数据总线宽度决定的。

字节(Byte)是计算机中用于表示数据的基本单位,通常由8位二进制数字组成。一个字节可以表示256种不同的值(从0到255)。字节在计算机中用于存储和处理各种类型的数据,如文本、图像、音频和视频等。

机器字长(Machine Word Length)是指计算机一次处理的二进制数位数。它决定了计算机能够处理的数据类型和运算速度。例如,32位机器字长表示计算机一次可以处理32位(4字节)的二进制数据,而64位机器字长表示计算机一次可以处理64位(8字节)的二进制数据。

实际 CPU 运行时,每一次控制内存读写信号发生时,CPU 可以从内存中读取数据总线宽度的数据,并将其写入到 CPU 的通用寄存器中。比如 32 位 CPU,机器字长为 4 字节,数据总线宽度为 32 位,如果该 CPU 的地址总线宽度也是为 32 位,则其可以访问的地址空间为 [0,0xffffffff]。

内存对齐的主要目的是为了减少 CPU 访问内存的次数,加大 CPU 访问内存的吞吐量。假设读取 8 个字节的数据,按照每次读取 4 个字节的速度,则 8 个字节需要 CPU 耗费 2 次读取操作。CPU 始终以字长访问内存,如果不进行内存对齐,很可能增加 CPU 访问内存的次数。

举个栗子,

如果内存中按照非对齐方式存储数据,如果我们要读取b这个数据的时候,就需要读取两次。第一次读取0-3的位置,得到存储在3的一个字节,再读取4-7位得到后三个字节。这样无疑会增大读取的次数。(图片来自力扣)

对齐的标准

  • 结构体变量的首地址能够被其最宽的基本类型成员的长度和对齐基数二者中的较小者所整除;
  • 结构体中的 static 成员变量不占用结构体的空间,由于静态成员变量在程序初始化时已经在静态存储区分配完成,所有该结构体实例中的静态成员都指向同一个内存区域;

  • 结构体每个成员相对于结构体首地址的偏移量 (offset) 都是该成员大小与对齐基数中的较小者的整数倍,如有需要编译器会在成员之间加上填充字节 (internal padding);

  • 结构体的总大小为结构体中最宽基本类型成员的长度与对齐基数二者中的较小者的整数倍,如有需要编译器会在最末尾的成员之后加上填充字节 (trailing padding);

  • 结构体中的函数不占用结构体的空间;

  • 带虚函数的类,有一个虚函数表指针,属于对象首部,与最大元素的大小对齐。、

结构体中的函数保存在程序的代码段中,而不是结构体的实例中。调用函数时,通过将this指针传递给函数来访问结构体实例的数据。同一个结构体(或者类)的不同对象(实例)在调用相同的成员函数时,调用的是内存中的同一个函数;也就是说在内存中,每个成员函数只有一个,不同对象调用时传递的是不同的this指针。

指定程序对齐规则

我们可以指定结构体的对齐规则,在某些特定场景下我们需要指定结构体内存进行对齐,比如在发送特定网络协议报文、硬件协议控制、消息传递、硬件寄存器访问时,这时就就需要避免内存对齐,因为双方均按照预先定义的消息格式来进行交互,从而避免不同的硬件平台造成的差异,同时能够将双方传递的数据进行空间压缩,避免不必要的空间浪费

programpack: 我们可以用 #progma pack(x) 指定结构体以 x 为单位进行对齐。

#pragma pack(push)
#pragma pack(x)
...
#pragma pack(pop)

举个栗子,

#pragma pack(push,4)
struct x1{
    char a;//1
    int b;//4
    double c;//8
    short e;//2
};
#pragma pack(pop)
struct x2{
    char a;//1
    int b;//4
    double c;//8
    short e;//2
};

int main() {

    std::cout << sizeof(x1);

    std::cout << sizeof(x2);

    return 0;
}

默认的pack是8。但是预处理器指令#pragma指定x1的pack是4。所以输出是

20 24(不同数据类型的长度在注释中标出,不同系统和硬件可能输出不同,但是思路相同)

alignofC++ 11 以后新增 alignof 的特性,通过调用 alignof 返回当前变量的字节对齐方式。

#pragma pack(push,4)
struct x1{
    char a;//1
    int b;//4
    double c;//8
    short e;//2
};
#pragma pack(pop)
struct x2{
    char a;//1
    int b;//4
    double c;//8
    short e;//2
};

int main() {

    std::cout << alignof(x1);

    std::cout << alignof(x2);

    return 0;
}

输出4,8。

总结

内存对齐使得程序便于在不同的平台之间进行移植,因为有些硬件平台不能够支持任意地址的数据访问,只能在某些地址处取某些特定的数据,否则会抛出异常;另一方面提高内存的访问效率,因为 CPU 在读取内存时,是以块为单位进行读取。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值