为何C++中对齐后的内存地址存在间隙?
问题分析:C++ alignas对齐后数组分配的地址间隙问题
代码示例
#include <iostream> using namespace std; int main() { alignas(0x10000) int ptr[42]; cout << ptr << endl; if (auto ptr = aligned_alloc(0x1000, 42 * sizeof(int))) { cout << ptr << endl; free(ptr); } { using aligned_int alignas(0x1000) = int; auto ptr = new aligned_int; cout << ptr << endl; // delete ptr; auto ptr2 = new aligned_int; cout << ptr2 << endl; // delete ptr2; auto ptr1 = new aligned_int[5]; cout << ptr1 << endl; delete[] ptr1; delete ptr; delete ptr2; } return 0; }
运行输出
0x7ffe70d20000 0x1f0c000 0x1f0c000 0x1f0e000 0x1f10000
疑问
已知ptr2的起始地址为0x1f0c000,为何ptr1的起始地址是0x1f0e000而非0x1f0d000?是否存在相关机制导致该现象?由于内存按0x1000对齐,分配的变量应位于前一个变量之后0x1000的位置,无法理解为何存在0x1000的间隙。
解答
出现这个间隙主要有两个核心原因:
1. new[]数组分配的元数据预留空间
当使用new[]分配数组时,C++标准要求内存分配器在数组元素的起始地址之前预留一段元数据空间,用来存储数组的元素个数——这段数据是delete[]时用来逐个调用元素析构函数的关键。
对于对齐要求为0x1000的aligned_int类型,数组的起始地址(即ptr1的值)必须严格对齐到0x1000的倍数。如果分配器尝试从0x1f0d000开始分配,元数据(通常是4~8字节)会占用0x1f0d000到0x1f0d008的空间,导致后续数组元素的起始地址为0x1f0d008,不符合0x1000的对齐要求。因此分配器必须跳过0x1f0d000,直接找到下一个满足对齐要求的地址0x1f0e000,确保数组起始地址完全对齐。
2. 内存分配器的块管理策略
针对高对齐要求的对象(如aligned_int),主流内存分配器(如glibc的malloc)会采用特殊的分配逻辑:
- 为了满足对齐要求,分配器会将单个对象的分配块大小向上对齐到对齐粒度(即
0x1000),即使对象本身仅占4字节。这意味着ptr和ptr2各自占用了一个完整的0x1000大小的内存块(0x1f0c000~0x1f0cfff和0x1f0e000~0x1f0efff)。 - 分配器通常会避免拆分对齐粒度的块,以减少内存碎片并简化管理,因此中间的
0x1f0d000~0x1f0dfff块可能被标记为不可用,或者分配器直接跳过该区域分配下一个完整块。
综上,这两个机制共同导致了ptr1的地址不是预期的0x1f0d000,而是0x1f0e000。
内容的提问来源于stack exchange,提问作者jackwang
相关产品推荐
相关产品推荐

