Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

1. 内存对齐的基本原理

1.1. 什么是内存对齐

内存对齐指数据在内存中存放时,其起始地址必须是某个值的整数倍,该值被称为对齐模数(alignment modulus)。

1.2. 为什么需要内存对齐

  1. 硬件要求:部分CPU仅能从对齐的地址读取数据。
  2. 性能优化:对齐的数据访问速度更快。
  3. 平台兼容性:不同平台可能存在不同的对齐要求。

1.3. 对齐规则

1
2
3
4
5
6
struct Example {
char a; // 1字节,偏移0
int b; // 4字节,偏移需是4的倍数,故偏移4
double c; // 8字节,偏移需是8的倍数,故偏移8
};
// 总大小:1 + 3(填充) + 4 + 8 = 16字节

2. 内存对齐的详细规则

(64位操作系统)

2.1. 基本类型对齐要求

1
2
3
4
5
6
7
8
9
// 常见类型的对齐要求(以x86_64 Linux为例):
char // 1字节对齐
short // 2字节对齐
int // 4字节对齐
float // 4字节对齐
double // 8字节对齐
long // 8字节对齐
long long // 8字节对齐
指针 // 8字节对齐

2.2. 结构体对齐规则

1
2
3
4
5
6
7
8
9
10
11
struct S1 {
char c; // 1字节
// 填充3字节
int i; // 4字节
}; // 总大小:8字节

struct S2 {
int i; // 4字节
char c; // 1字节
// 填充3字节
}; // 总大小:8字节

2.3. 联合体对齐

1
2
3
4
5
union U {
int i; // 4字节
double d; // 8字节
char c; // 1字节
}; // 总大小:8字节(按最大成员对齐)

3. 控制内存对齐的方法

3.1. pack预处理指令

pack预处理指令有两种用法:

  • 方式一:#pragma pack(push, N) / #pragma pack(pop)
  • 方式二:#pragma pack(N) / #pragma pack()

3.1.1. #pragma pack(push, N) / #pragma pack(pop)

基本用法:

1
2
3
4
5
6
7
#pragma pack(push, 1)  // 保存当前对齐设置,并设置为1
struct TightPacked {
char a;
int b;
short c;
};
#pragma pack(pop) // 恢复之前保存的对齐设置

复杂用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <cstddef>

#pragma pack(push, 8) // 第1层:保存当前,设置为8
struct StructA {
char a; // 偏移 0
double b; // 偏移 8(如果8字节对齐)
int c; // 偏移 16
};

#pragma pack(push, 4) // 第2层:保存当前(8),设置为4
struct StructB {
char a; // 偏移 0
double b; // 偏移 4(4字节对齐时)
int c; // 偏移 12
};

#pragma pack(push, 1) // 第3层:保存当前(4),设置为1
struct StructC {
char a; // 偏移 0
double b; // 偏移 1(无填充)
int c; // 偏移 9
};

#pragma pack(pop) // 恢复第3层:回到4
struct StructD {
char a; // 偏移 0
double b; // 偏移 4(再次4字节对齐)
int c; // 偏移 12
};

#pragma pack(pop) // 恢复第2层:回到8
struct StructE {
char a; // 偏移 0
double b; // 偏移 8(回到8字节对齐)
int c; // 偏移 16
};

#pragma pack(pop) // 恢复第1层:回到原始设置

int main() {
std::cout << "Size of StructA (pack 8): " << sizeof(StructA) << std::endl;
std::cout << "Size of StructB (pack 4): " << sizeof(StructB) << std::endl;
std::cout << "Size of StructC (pack 1): " << sizeof(StructC) << std::endl;
std::cout << "Size of StructD (back to 4): " << sizeof(StructD) << std::endl;
std::cout << "Size of StructE (back to 8): " << sizeof(StructE) << std::endl;

return 0;
}

运行结果:

1
2
3
4
5
Size of StructA (pack 8): 24
Size of StructB (pack 4): 16
Size of StructC (pack 1): 13
Size of StructD (back to 4): 16
Size of StructE (back to 8): 24

3.1.2. #pragma pack(N) / #pragma pack()

基本用法:

1
2
3
4
5
6
7
#pragma pack(1)       // 设置为1字节对齐(不保存状态)
struct TightPacked {
char a;
int b;
short c;
};
#pragma pack() // 恢复默认对齐(通常是8)

复杂用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <cstddef>
#include <iostream>

#pragma pack(8) // 第1层:保存当前,设置为8
struct StructA
{
char a; // 偏移 0
double b; // 偏移 8(如果8字节对齐)
int c; // 偏移 16
};

#pragma pack(4) // 第2层:保存当前(8),设置为4
struct StructB
{
char a; // 偏移 0
double b; // 偏移 4(4字节对齐时)
int c; // 偏移 12
};

#pragma pack(1) // 第3层:保存当前(4),设置为1
struct StructC
{
char a; // 偏移 0
double b; // 偏移 1(无填充)
int c; // 偏移 9
};

#pragma pack() // 恢复第3层:回到4
struct StructD
{
char a; // 偏移 0
double b; // 偏移 4(再次4字节对齐)
int c; // 偏移 12
};

#pragma pack() // 恢复第2层:回到8
struct StructE
{
char a; // 偏移 0
double b; // 偏移 8(回到8字节对齐)
int c; // 偏移 16
};

#pragma pack() // 恢复第1层:回到原始设置

int main()
{
std::cout << "Size of StructA (pack 8): " << sizeof(StructA) << std::endl;
std::cout << "Size of StructB (pack 4): " << sizeof(StructB) << std::endl;
std::cout << "Size of StructC (pack 1): " << sizeof(StructC) << std::endl;
std::cout << "Size of StructD (back to 4): " << sizeof(StructD) << std::endl;
std::cout << "Size of StructE (back to 8): " << sizeof(StructE) << std::endl;

return 0;
}

运行结果:

1
2
3
4
5
Size of StructA (pack 8): 24
Size of StructB (pack 4): 16
Size of StructC (pack 1): 13
Size of StructD (back to 4): 24
Size of StructE (back to 8): 24

3.1.3. 总结概述

功能:

两组指令都实现相同的目标。

  1. 将结构体/类成员的对齐方式设置为N字节
  2. 然后恢复到之前的对齐设置

区别:

  • 在处理简单单层的对齐设置时,表现几乎相同。
  • 在处理多层嵌套的对齐设置时,方式一可以完美支持,某些编译器下方式二可能无法正确恢复嵌套的对齐设置。
  • 建议:尽量使用方式一(push/pop),更安全、更清晰。

注意事项:

  • pop必须与push配对,否则会导致编译错误或意外行为
  • 跨文件边界要小心,确保在同一个编译单元内完成push/pop对

3.2. __attribute__预处理指令

3.2.1. __attribute__((packed))

__attribute__((packed))是GCC/Clang编译器的扩展属性,用于取消结构体的内存对齐,使其成员按最小可能对齐方式(1字节对齐)紧密排列。

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 应用于结构体定义
struct __attribute__((packed)) PackedStruct {
char a;
int b;
double c;
};

// 2. 应用于结构体末尾(GCC语法)
struct PackedStruct {
char a;
int b;
double c;
} __attribute__((packed));

// 3. 应用于typedef
typedef struct __attribute__((packed)) {
char a;
int b;
} PackedType;

// 4. 应用于变量声明
struct MyStruct {
char a;
int b;
} my_var __attribute__((packed));

3.2.2. __attribute__((aligned(N)))

__attribute__((aligned(N))) 是GCC/Clang编译器的扩展属性,用于指定变量、类型或成员的对齐方式,强制要求按指定的字节数对齐。如:aligned(16)表示按16字节边界对齐。

基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 应用于变量
int __attribute__((aligned(16))) aligned_var;

// 2. 应用于数组
double __attribute__((aligned(32))) matrix[1024];

// 3. 应用于结构体类型
struct __attribute__((aligned(16))) AlignedStruct {
int a;
char b;
};

// 4. 应用于结构体末尾
struct AnotherStruct {
int x;
double y;
} __attribute__((aligned(32)));

// 5. 应用于结构体成员
struct MemberAligned {
char a;
int b __attribute__((aligned(16)));
double c;
};

// 6. 组合使用
struct __attribute__((packed, aligned(16))) Mixed {
char a;
int b;
};

3.3. C11/C++11的alignas用法

TODO

4. 应用场景

  • 文件格式处理
  • 网络数据传输
  • 嵌入式系统特殊硬件要求等

评论