C++ 内存对齐

内存对齐

引言

理论上,计算机可以访问任何内存地址上的数据。但实际上,系统会对变量的存放地址施加限制,通常要求变量首地址是某个特定值(称为对齐模数 N)的整数倍。这种限制就是内存对齐。

为什么要求内存对齐

主要原因有两个:

  1. 硬件平台限制:不同硬件平台对内存的存取单位可能不同。为了确保处理器能够正确存取数据,必须进行对齐。
  2. 提高CPU内存访问速度:处理器通常以固定的粒度访问内存。如果数据没有对齐,CPU可能需要两次内存访问才能读取完整数据;而对齐后,可以一次性读取,提高效率。

内存对齐规则

内存对齐的对象是所有数据类型 ,包括基本类型(如 intdouble)、数组、结构体(struct)和类(class)。

详细说明:

  1. 基本类型 :每个基本类型都有自身的对齐要求。例如,一个 int(通常4字节)通常需要存储在4的整数倍地址上;double(8字节)通常需要8字节对齐。
  2. 结构体(struct)和类(class):在C++中,结构体和类在内存布局上本质相同(仅默认访问权限不同)。它们作为复合类型,其内存对齐规则涉及内部各成员的对齐以及整体的对齐。
  3. 数组 :数组的对齐要求与其元素类型一致。例如,int 数组的每个元素都按 int 的对齐要求存放。

基本类型的对齐要求

类型 大小(字节) 对齐要求(字节)
char 1 1
short 2 2
int 4 4
float 4 4
double 8 8
long 4(32位)/8(64位) 4 或 8
指针 4/8 4 或 8

对齐要求通常等于类型的大小,但并非绝对(例如 long double 可能对齐为 16 字节)。

结构体与类的对齐

默认对齐规则
  1. 每个成员必须放在其自身对齐要求的整数倍地址上。
  2. 整个结构体的大小必须是最大成员对齐要求的整数倍。
  3. 成员按声明顺序排列,编译器在必要时插入填充字节(padding)。
cpp 复制代码
struct MyStruct {
    char a;      // 1字节
    int b;       // 4字节
    double c;    // 8字节
};

class MyClass {
    char a;
    int b;
    double c;
public:
    void foo() {}
};

// 在相同编译环境下,MyStruct 和 MyClass 的内存布局和对齐要求通常一致。
复制代码
MyStruct/MyClass 内存布局(假设起始地址为0):
+----+----+----+----+----+----+----+----+
| a  | padding |         b              |   // 偏移0-7
+----+----+----+----+----+----+----+----+
|               c                       |   // 偏移8-15
+----+----+----+----+----+----+----+----+

详细说明:
1. char a: 偏移0,占用1字节
2. 填充: 偏移1-3,3字节,为了满足int b的4字节对齐
3. int b: 偏移4-7,4字节
4. double c: 偏移8-15,8字节,同时已经是8的倍数

总大小:16字节
使用#pragma pack对齐

某些场景(如网络协议、硬件寄存器、文件格式)需要取消填充字节 ,使结构体紧密排列。编译器提供了打包指令 #pragma pack

网络协议传输中,如果把 padding 中无意义的数据打包进入,可能导致异常

基本语法

cpp 复制代码
// 设置对齐值为 n(n 通常是 1、2、4、8、16 等 2 的幂)
#pragma pack(n)

// 保存当前对齐设置并设置新值(推荐用法)
#pragma pack(push, n)

// 恢复之前保存的对齐设置
#pragma pack(pop)

// 恢复编译器默认对齐(通常是 8)
#pragma pack()

示例

cpp 复制代码
// 默认对齐下,结构体大小为24字节
struct DefaultAlign {
    char a;      // 1字节 + 7字节填充
    double b;    // 8字节
    int c;       // 4字节 + 4字节填充(整体需是8的倍数)
}; // sizeof = 24

// 使用4字节对齐,大小减少到16字节
#pragma pack(4)
struct Pack4Align {
    char a;      // 1字节 + 3字节填充
    double b;    // 8字节(按4对齐,分两次存储)
    int c;       // 4字节
}; // sizeof = 16
#pragma pack()

打包的代价

  • 访问非对齐成员可能引发性能下降甚至硬件异常(若平台不支持非对齐访问)。
  • 某些处理器会静默修复非对齐访问,但需要额外微码操作,速度慢。
  • 跨平台可移植性:同一份打包结构在不同架构上行为可能不同。

参考资料

相关推荐
柒儿吖1 小时前
三方库 Boost.Regex 在 OpenHarmony 的 lycium完整实践
c++·c#·openharmony
老毛肚1 小时前
java juc 01 进程与线程
java·开发语言
1candobetter2 小时前
JAVA后端开发——反射机制在Spring业务开发中的实际应用
java·开发语言·spring
一只小小的芙厨2 小时前
寒假集训·子集枚举2
c++·笔记·算法·动态规划
野犬寒鸦2 小时前
WebSocket协同编辑:高性能Disruptor架构揭秘及项目中的实战应用
java·开发语言·数据库·redis·后端
kyle~2 小时前
ROS2----组件(Components)
开发语言·c++·机器人·ros2
阿猿收手吧!2 小时前
【C++】Ranges 工厂视图与投影机制
开发语言·c++
.小墨迹2 小时前
局部规划中的TEB,DWA,EGOplanner等算法在自动驾驶中应用?
开发语言·c++·人工智能·学习·算法·机器学习·自动驾驶
哈基咩2 小时前
从零搭建校园活动平台:go-zero 微服务实战完整指南
开发语言·微服务·golang