关于placement new与内存对齐偏移的技术问询
聊一聊Placement New的内存对齐那些事儿
嘿,看你在研究placement new的内存对齐机制,还写了测试代码,刚好我对这块摸得比较透,来跟你拆解清楚~
先看你代码里的结构体对齐逻辑
你定义的两个结构体A和B,它们的对齐要求是由成员里对齐最严格的类型决定的:
struct A里有double(通常是8字节对齐),所以整个A的对齐边界是8字节,大小会被编译器填充到16字节(8字节的double + 5字节的char数组,凑够8的倍数就得补3字节)struct B里最严格的也是double,所以对齐边界同样是8字节,算下来大小是24字节(float4+int4=8,char[2]占2,得补6字节凑8的倍数,再加double的8,总共24)
然后你用new unsigned char[1024]分配了一块内存,应该是打算用placement new在上面构造对象对吧?不过你main函数没写完没关系,咱们重点说对齐的核心问题。
Placement New的对齐本质:它不帮你调地址!
标准库里的placement new(就是<new>头文件里那个void* operator new(std::size_t, void* ptr) noexcept;)其实就是个“搬运工”——它啥对齐处理都不做,直接把你传进去的ptr原封不动返回。所以你必须自己保证传入的地址完全符合要构造对象的对齐要求,不然构造出来的对象会触发未定义行为(比如访问崩溃、数据乱码啥的)。
举个例子:如果new unsigned char[1024]返回的地址刚好是8字节对齐(大部分平台默认是这样),那构造A和B都没问题,但要是你手动把mem偏移了几个字节,比如mem+3,那这个地址就不满足8字节对齐了,直接用placement new构造A就会出问题。
怎么确保内存地址对齐?
给你几个实用的方法:
- 用
alignas强制指定对齐:比如在定义内存的时候直接加对齐属性,确保起始地址符合要求:alignas(std::max_align_t) unsigned char* mem = new unsigned char[1024];std::max_align_t是标准库定义的类型,它的对齐要求是平台上所有基本类型里最严格的,用它准没错。 - 用
std::aligned_alloc分配对齐内存(C11及以上可用):比如要分配16字节对齐的1024字节内存:void* aligned_mem = std::aligned_alloc(16, 1024); - C++17+用
std::aligned_storage:可以直接定义一个指定对齐和大小的存储类型,专门用来放对象:using AStorage = std::aligned_storage_t<sizeof(A), alignof(A)>; AStorage a_storage; A* a = new (&a_storage) A;
给你补个完整的测试代码
你可以把这段代码跑起来,直观看到对齐的情况:
#include <iostream> #include <cstdint> #include <new> #include <type_traits> using namespace std; unsigned char* mem = nullptr; struct A { double d; char c[5]; }; struct B { float f; int a; char c[2]; double d; }; void InitMemory() { // 用alignas确保内存起始地址对齐到最大要求 alignas(std::max_align_t) static unsigned char buf[1024]; mem = buf; } int main() { InitMemory(); // 打印地址和对齐要求 cout << "mem起始地址: " << reinterpret_cast<uintptr_t>(mem) << endl; cout << "A的对齐要求: " << alignof(A) << endl; cout << "B的对齐要求: " << alignof(B) << endl; // 检查mem是否满足A的对齐 bool a_aligned = (reinterpret_cast<uintptr_t>(mem) % alignof(A)) == 0; cout << "mem能对齐构造A吗?" << (a_aligned ? "必须能!" : "不行哦") << endl; // 用placement new构造A A* a = new (mem) A; cout << "A对象的地址: " << reinterpret_cast<uintptr_t>(a) << endl; // 构造B的时候,手动调整地址到对齐边界 uintptr_t b_raw_addr = reinterpret_cast<uintptr_t>(mem) + sizeof(A); // 对齐到B的要求:计算下一个对齐的地址 uintptr_t b_aligned_addr = (b_raw_addr + alignof(B) - 1) & ~(alignof(B) - 1); B* b = new (reinterpret_cast<void*>(b_aligned_addr)) B; cout << "B对象的地址: " << reinterpret_cast<uintptr_t>(b) << endl; // 别忘了手动销毁对象(placement new不会自动调用析构) b->~B(); a->~A(); return 0; }
最后划重点
- Placement new只管把你给的地址返回,完全不处理对齐,对齐的锅得你自己背
- 一定要确保传入的地址符合
alignof(你的对象类型)的要求,不然就是未定义行为 - 用
alignas、std::aligned_alloc这些工具可以轻松拿到对齐的内存块
内容的提问来源于stack exchange,提问作者Acorn
相关产品推荐
相关产品推荐

