使用placement new操作符时我真的需要担心对齐吗?

Posted

技术标签:

【中文标题】使用placement new操作符时我真的需要担心对齐吗?【英文标题】:Do I really have to worry about alignment when using placement new operator? 【发布时间】:2012-07-31 16:13:20 【问题描述】:

我读到了这个When should I worry about alignment?,但我仍然不知道我是否需要担心放置 new 运算符返回的指针不对齐 - 就像在这个例子中一样:

class A 
public:
   long double a;
   long long b;
   A() : a(1.3), b(1234) 
;

char buffer[64];

int main() 
   // (buffer + 1) used intentionally to have wrong alignment
   A* a = new (buffer + 1) A(); 
   a->~A();

__alignof(A) == 4(buffer + 1) 未与 4 对齐。但一切正常 - 完整示例在这里:http://ideone.com/jBrk8

如果这取决于架构,那么我使用的是:linux/powerpc/g++ 4.x.x。

[更新] 在发布这个问题后,我阅读了这篇文章:http://virtrev.blogspot.de/2010/09/memory-alignment-theory-and-c-examples.html。 也许在我的情况下唯一的缺点是性能损失,我的意思是未对齐的访问成本高于对齐?

【问题讨论】:

x86 对对齐问题特别宽容,而 powerpc 则不然。 Placement new 只是返回给定的指针。 未对齐的访问通常受支持但速度较慢。 x86 就是这种情况,但我不知道 PPC。 @FatalError,对于今天的缓存架构,我不确定这是否是真的。如果您越过缓存线边界,您肯定会看到命中。 @R.MartinhoFernandes,它还调用了在这种情况下设置一些成员的构造函数。如果有问题,它应该触发它。 【参考方案1】:

仅仅因为一切看起来都有效并不意味着它实际上可以。

C++ 是一个定义需要 工作的规范。编译器还可以使不需要的东西工作。这就是“未定义行为”的含义:任何事情都可能发生,因此您的代码不再可移植。

C++ 并不要求这个工作。所以,如果你把你的代码带到一个编译器上,但编译器还是不行,你就不能再责怪C++了。滥用语言是你的错。

【讨论】:

你的意思是在placement new中使用未对齐的指针是UB吗?你能澄清一下哪个标准是这样说的:C++03 还是 C++11? C++11 § 3.11\2 “对象类型具有对齐要求(3.9.1、3.9.2),这些要求限制了可以分配该类型对象的地址。”和 § 3.11\8 “如果实现不支持在特定上下文中对特定扩展对齐的请求,则程序格式错误。此外,请求的动态存储的运行时分配请求不能满足所请求的对齐应视为分配失败。”【参考方案2】:

是的,这完全取决于架构,可能还取决于编译器优化标志。 在您执行 A b = a; 或其他一些随机访问,这些访问被编译为某些 movdqa 操作并且您的程序崩溃之前,一切都会正常工作。

【讨论】:

【参考方案3】:

有些处理器肯定会因此而崩溃(例如 sparc),有些则根本不关心,有些处理器会很慢。 C++ 假设您知道自己在做什么(或不知道),就像 reinterpret_cast 等一样。

【讨论】:

【参考方案4】:

一个快速的 google 告诉我 powerpc 上的 gcc 有一个选项来告诉编译器是否由系统处理未对齐的访问。

我认为在任何一种情况下,该平台上的未对齐访问都会非常缓慢,应尽可能避免。

【讨论】:

【参考方案5】:

当你在缓冲区上调用placement new时:

A *a = new (buf) A;

您正在调用内置的void* operator new (std::size_t size, void* ptr) noexcept,定义如下:

c++11

18.6.1.3 展示位置表格 [new.delete.placement]

这些函数是保留的,C++ 程序不能定义替换版本的函数 标准 C++ 库 (17.6.4)。 (3.7.4) 的规定不适用于这些保留的放置形式 运算符 new 和运算符 delete。void* operator new(std::size_t size, void* ptr) noexcept; 返回:ptr. 备注:故意不执行其他动作。

(3.7.4) 的规定 包括返回的指针应该适当对齐,因此如果传入一个指针,void* operator new (std::size_t size, void* ptr) noexcept 可以返回一个未对齐的指针。这并不不过,让摆脱困境:

5.3.4 新 [expr.new]

[14] 注意:分配函数返回非null值时,必须是指向存储块的指针 为对象预留的空间。假定存储块已适当对齐 和要求的大小。

因此,如果您将未对齐的存储传递给放置新表达式,则您违反了存储已对齐的假设,结果是 UB。


确实,在您上面的程序中,如果您将long long b 替换为__m128 b(在#include <xmmintrin.h> 之后),那么程序将会出现段错误,正如预期的那样。

【讨论】:

这是什么意思?这是否意味着我们必须将新地址传递给放置缓冲区 + 偏移量,其中 (buffer + 偏移量) class== 0 的 % 对齐? @bysreg 当然可以,只要您确保剩余空间足够。或者,您可以使用提供已对齐缓冲区的工具,例如对齐存储或 posix_memalign。

以上是关于使用placement new操作符时我真的需要担心对齐吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++内存管理(new operator/operator new/operator delete/placement new)

C++中的newoperator new与placement new

placement new的用法及用途

Placement new的用法及用途

C++内存分配方法new与placement new使用方法详解

Item 52:写了placement new就要写placement delete