Dynamically allocate properly-aligned memory: is the new-expression on char arrays suitable?

人盡茶涼 提交于 2019-12-12 15:07:53

问题


I am following Stefanus Du Toit's hourglass pattern, that is, implementing a C API in C++ and then wrapping it in C++ again. This is very similar to the pimpl idiom, and it is also transparent to the user, but prevents more ABI-related issues and allows for a wider range of foreign language bindings.

As in the pointer-to-implementation approach, the underlying object's size and layout is not known by the outsiders at compile-time, so the memory in which it resides has to be dynamically allocated (mostly). However, unlike in the pimpl case, in which the object has been fully defined at the allocation point, here its properties are completely hidden behind an opaque pointer.

Memory obtained with std::malloc is "suitably aligned for any scalar type", which makes it unsuitable for the task. I'm not sure about the new-expression. Quoted from the section Allocation of the linked page:

In addition, if the new-expression is used to allocate an array of char or an array unsigned char, it may request additional memory from the allocation function if necessary to guarantee correct alignment of objects of all types no larger than the requested array size, if one is later placed into the allocated array.

Does this mean that the following code is compliant?

C API

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();

C++ wrapper

/* 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;

If it is not, how to dynamically allocate properly-aligned memory?


回答1:


I can't find where the standard guarantees your proposed code to work. First, I cannot find the part of the standard that backs what you've quoted from CppReference.com, but even if we take that claim on faith, it still only says that it may allocate additional space. If it doesn't, you're sunk.

The standard does speak to the alignment of memory returned by operator new[]: "The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type …." (C++03, §3.7.2.1/2; C++11, §3.7.4.1/2) However, in the context where you're planning to allocate the memory, the type you plan to store in it isn't a complete type. And besides, the result of operator new[] isn't necessarily the same as the result of the new-expression new char[…]; the latter is allowed to allocate additional space for its own array bookkeeping.

You could use C++11's std::align. To guarantee that you allocate space that can be aligned to the required amount, you'd have to allocate object_size() + object_alignment() - 1 bytes, but in practice, allocating only object_size() bytes will probably be fine. Thus, you might try using std::align something like this:

size_t buffer_size = object_size();
void* p = operator new(buffer_size);
void* original_p = p;
if (!std::align(object_alignment(), object_size(), p, buffer_size) {
  // Allocating the minimum wasn't enough. Allocate more and try again.
  operator delete(p);
  buffer_size = object_size() + object_alignment() - 1;
  p = operator new(buffer_size);
  original_p = p;
  if (!std::align(object_alignment(), object_size(), p, buffer_size)) {
    // still failed. invoke error-handler
    operator delete(p);
  }
}
object_construct(p);
object_destruct(p);
operator delete(original_p);

The allocator described in another question accomplishes much the same thing. It's templated on the type of object being allocated, which you don't have access to, but it's not required to be that way. The only times it uses its template type argument are to evaluate sizeof and alignof, which you already have from your object_size and object_alignment functions.

That seems like a lot to require from consumers of your library. It would be much more convenient for them if you moved the allocation behind the API as well:

void* object_construct() {
  return new internal_object();
}

Make sure to move the destruction, too, by calling delete, not just the destructor.

That makes any alignment questions go away because the only module that really needs to know it is the one that already knows everything else about the type being allocated.



来源:https://stackoverflow.com/questions/26471718/dynamically-allocate-properly-aligned-memory-is-the-new-expression-on-char-arra

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!