UE C++ 里的内存对齐
基础
struct 和 class 的内存大小
两个决定因素:
- 每个成员变量占据一定的字节数
- 成员变量之间的padding,内存对齐
一个简单的结构体示例:
cpp
struct Example2 {
char a; // 1 byte
double b; // 8 bytes
int c; // 4 bytes
};
它的sizeof 是 24 bytes, alignof 是 8 bytes。
它的内存布局可以看出,它是用最大的成员(double b)来对齐的:
txt
偏移量: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
内容: | a |pad |pad |pad |pad |pad |pad |pad | b | b | b | b | b | b | b | b | c | c | c | c |pad |pad |pad |pad |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
说明: |<-- char -->|<------ padding ----->|<---------- double ---------->|<---- int ---->|<---- padding ---->|
|<-------------------------- 24 bytes total --------------------------->|
这造成了非常多无用的padding,导致内存浪费。可以调整成员变量的位置来减少浪费:
cpp
struct Example2Optimized {
double b; // 8 bytes
int c; // 4 bytes
char a; // 1 byte
// 只需要 3 字节的填充
};
优化后的内存布局:
txt
偏移量: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
内容: | b | b | b | b | b | b | b | b | c | c | c | c | a |pad |pad |pad |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|<---------- double ---------->|<---- int ---->|<-char->|<-padding->|
|<---------------------- 16 bytes total ---------------------->|
它的sizeof 是 16 bytes, alignof 是 8 bytes。大大减少了内存浪费。
尽量将大尺寸类型放在前面可以减少填充字节。
C++ 11 的 alignas 和 alignof
上面的示例可以看出,alignof 通常返回成员变量中最大的成员的大小。 alignas
是一个 C++11引入的关键字,可以用来显式指定 class
或 struct
的对齐方式。
示例:
cpp
#include <iostream>
#include <cstddef>
alignas(16) class AlignedExample {
char c; // 1字节
int i; // 4字节
};
int main() {
std::cout << "Alignment of AlignedExample: " << alignof(AlignedExample) << std::endl; // 输出 16
return 0;
}
上述代码中,alignas(16) 强制 AlignedExample 的对齐为 16 字节。
还可以:
cpp
alignas(8) uint8 Data[14]; // Data数组起始地址将按8字节对齐
struct alignas(16) VectorRegister4Double { ... } // 结构体按16字节对齐
class alignas(16) FAABBVectorized { ... } // 类按16字节对齐
用于指定变量的内存对齐时:
// 假设我们在内存中连续声明以下变量
uint8 normalVar; // 假设被分配到地址 1001
alignas(8) uint8 alignedData[14]; // 会被分配到第一个能被8整除的地址,比如 1008
uint8 anotherVar; // 假设被分配到地址 1022
图解说明:
txt
内存地址: 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 ... 1021 1022
|
+----- alignedData的起始地址
(确保是8的倍数)
看下每个变量的位置:
1001: normalVar
1008: alignedData[0] <-- 注意这里是8的倍数
1009: alignedData[1]
1010: alignedData[2]
...
1021: alignedData[13]
1022: anotherVar
UE 的 Align 模板函数
简单地说,这个函数用于将一个值向上对齐到Alignment的倍数。比如:
- Align(5, 4) = 8
- Align(8, 4) = 8
- Align(9, 4) = 12
这段代码确保ComponentDataPtr按照缓存行大小对齐:
cpp
uint8 Alignment = FMath::Max<uint8>(PLATFORM_CACHE_LINE_SIZE, TypeInfo.Alignment);
ComponentDataPtr = Align(ComponentDataPtr, Alignment);
假设 ComponentDataPtr 当前指向地址 1005
Alignment 是 8
那么 Align(1005, 8) 会返回 1008
txt
// 内存示意图:
// 地址: 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
// ^ ^
// | |
// 当前位置 对齐后位置