动态分配正确对齐的内存:char 数组上的 new 表达式是否合适?

Kal*_*ish 6 c++ memory-management memory-alignment

我遵循 Stefanus Du Toit 的沙漏模式,即在 C++ 中实现 C API,然后再次将其包装在 C++ 中。这与pimpl idiom非常相似,它对用户也是透明的,但可以防止更多与 ABI 相关的问题并允许更广泛的外语绑定。

与实现指针的方法一样,底层对象的大小和布局在编译时不被外界所知,因此它所在的内存必须动态分配(主要是)。然而,与pimpl情况不同,在pimpl情况下,对象已在分配点完全定义,这里它的属性完全隐藏在不透明指针后面。

获得的内存std::malloc是“适合任何标量类型”,这使得它不适合任务。我不确定new-expression。引自链接页面的分配部分:

此外,如果 new 表达式用于分配一个 char 数组或一个 unsigned char 数组,它可能会在必要时从分配函数中请求额外的内存,以保证不大于请求的数组大小的所有类型的对象的正确对齐,如果稍后将一个放入分配的数组中。

这是否意味着以下代码是合规的?

编程接口

size_t object_size      ( void );     // return sizeof(internal_object);
size_t object_alignment ( void );     // return alignof(internal_object);
void   object_construct ( void * p ); // new (p) internal_object();
void   object_destruct  ( void * p ); // static_cast<internal_object *>(p)->~internal_object();
Run Code Online (Sandbox Code Playgroud)

C++ 包装器

/* The memory block that p points to is correctly aligned
   for objects of all types no larger than object_size() */
auto p = new char[ object_size() ];
object_construct( p );
object_destruct( p );
delete[] p;
Run Code Online (Sandbox Code Playgroud)

如果不是,如何动态分配正确对齐的内存?

Rob*_*edy 1

我找不到标准在哪里保证您提出的代码可以工作。首先,我找不到支持您从 CppReference.com 引用的内容的标准部分,但即使我们相信这一主张,它仍然只说它可以分配额外的空间。如果没有,你就沉没了。

\n\n

该标准确实谈到了返回的内存对齐operator new[]:“返回的指针应适当对齐,以便它可以转换为任何完整对象类型\xe2\x80\xa6的指针。” (C++03, \xc2\xa73.7.2.1/2; C++11, \xc2\xa73.7.4.1/2) 但是,在您计划分配内存的情况下,您计划存储在其中的类型不是完整类型。此外, 的结果operator new[]不一定与 new-expression 的结果相同new char[\xe2\x80\xa6];后者可以为其自己的数组簿记分配额外的空间。

\n\n

您可以使用 C++11 的std::align. 为了保证您分配的空间可以与所需的数量对齐,您必须分配object_size() + object_alignment() - 1字节,但实际上,仅分配object_size()字节可能就可以了。因此,您可以尝试使用std::align这样的东西:

\n\n
size_t buffer_size = object_size();\nvoid* p = operator new(buffer_size);\nvoid* original_p = p;\nif (!std::align(object_alignment(), object_size(), p, buffer_size) {\n  // Allocating the minimum wasn\'t enough. Allocate more and try again.\n  operator delete(p);\n  buffer_size = object_size() + object_alignment() - 1;\n  p = operator new(buffer_size);\n  original_p = p;\n  if (!std::align(object_alignment(), object_size(), p, buffer_size)) {\n    // still failed. invoke error-handler\n    operator delete(p);\n  }\n}\nobject_construct(p);\nobject_destruct(p);\noperator delete(original_p);\n
Run Code Online (Sandbox Code Playgroud)\n\n

另一个问题中描述的分配器完成了几乎相同的事情。它是根据所分配的对象类型进行模板化的,您无权访问该对象,但并不要求如此。它使用其模板类型参数的唯一时间是评估sizeofalignof,您已经从object_sizeobject_alignment函数中获得了它。

\n\n

这似乎对图书馆的消费者提出了很多要求。如果您也将分配移到 API 后面,对他们来说会更方便:

\n\n
void* object_construct() {\n  return new internal_object();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

确保也通过调用 来移动销毁delete,而不仅仅是析构函数。

\n\n

这使得任何对齐问题都消失了,因为唯一真正需要知道它的模块是已经知道有关所分配类型的其他所有内容的模块。

\n