仅持有首个对象指针,如何访问同块超大分配内存中的第二个异构对象?
问题:仅持有
t指针时能否获取u指针? 请考虑以下代码:
struct T { int a = 1; }; struct U { char b = 2; }; static_assert(alignof(U) == 1); // 分配内存。通过P0593转换以访问unsigned char的隐式数组。 auto* s = reinterpret_cast<unsigned char*>(::operator new(sizeof(T) + sizeof(U))); // 在该内存中构造T和U对象。 T* t = new (s) T; U* u = new (s + sizeof(T)) U; // 仅持有t而不使用s时,是否有合法方式获取指向u的指针? U* u2 = reinterpret_cast<U*>(reinterpret_cast<unsigned char*>(t) + sizeof(T));
核心疑问:
- 仅持有
t时,能否合法获取u? - 能否从
t恢复底层完整内存块s? - 如果要一次性分配内存存储异构非数组对象,是否必须在
t中存储u的指针才能后续恢复?
结论:仅持有t时,无法合法获取u或恢复完整内存块s
原因解析:
对象边界与内存所有权分离
t指向的是独立的T类型对象,C++标准没有规定如何从单个对象指针反向推导其所在的更大内存分配块的起始地址,或其他无关对象的位置。operator new返回的内存块和构造在其中的对象是两个独立概念——对象仅“占用”自身大小的内存,标准不提供从对象回溯到分配块的机制。std::launder无法解决问题std::launder的可达性要求是关键:它仅允许访问同一内存块中可通过原指针可达的对象,但u并不在t指向对象的可达范围内(T中没有指向U的成员,也无继承或其他关联关系),因此std::launder无法帮你从t获取u。指针偏移属于未定义行为
代码中尝试的reinterpret_cast<unsigned char*>(t) + sizeof(T)是未定义行为:虽然T对象的起始地址和s一致,但从t转换来的unsigned char*仅能合法访问sizeof(T)范围内的内存(即T对象的对象表示)。超出该范围的内存访问(包括计算指向u的指针)没有标准保障,编译器优化可能导致结果不符合预期。
解决方案:
如果需要在仅持有t的情况下访问u,必须显式存储关联信息:
- 在
T结构体中添加指向U的指针成员,构造T时传入并保存u的地址; - 或者在
T中存储内存块起始地址s,后续通过偏移计算得到u; - 若
U大小固定,可使用聚合结构管理,但这不符合你提到的“U大小动态”的场景。
内容的提问来源于stack exchange,提问作者Brad Spencer
相关产品推荐
相关产品推荐

