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()

打包的代价

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

参考资料

相关推荐
ZhangShao06073 分钟前
题解:CF2249B
c++·算法
用户昵称10031 分钟前
C/C++编程-工程实践-本地存储log的工程意义
c语言·开发语言
吹什么轩2 小时前
c++复习:map和set的使用
开发语言·c++
必须得开心呀2 小时前
qt生成dump文件并定位异常
开发语言·qt
fpcc2 小时前
跟我学C++中级篇—内存流
开发语言·c++
Cicada1282 小时前
ccvt:一个用 Rust 写的中国地图坐标系互转命令行工具
开发语言·后端·rust
1001101_QIA2 小时前
工控机网络配置
开发语言·数据库·php
程序员雷欧3 小时前
ThreadPoolExecutor 深度解析:从核心参数到源码实现的全面剖析
java·开发语言·jvm
catchadmin3 小时前
对标 npx 的 CPX PHP 的 Composer 包执行器
开发语言·php·composer