Dynamically allocate correctly aligned memory: is the new expression suitable for char arrays?

I follow the Stefanus Du Toit hourglass style, that is, implement the C API in C ++, and then finish it again in C ++. This is very similar to the pimpl idiom, and it is also transparent to the user, but it prevents more problems with ABI and allows you to use a wider range of bindings to a foreign language.

As in the approach with a pointer to implementation, the size and layout of the main object are unknown to outsiders at compile time, so the memory in which it is located should be dynamically allocated ( basically ). However, unlike the case of pimpl, in which the object was completely defined at the point of selection, here its properties are completely hidden behind an opaque pointer.

The memory obtained with std::malloc is “appropriately aligned for any scalar type”, which makes it unsuitable for the task. I am not sure about the new expression . Quoted from the Allocation section on the linked page:

In addition, if a new expression is used to allocate a char array or an unsigned array, it can request additional memory from the distribution function, if necessary, to ensure proper alignment, objects of all types do not exceed the size of the requested array, if one is then placed in the selected array .

Does this mean that the following code is compatible?

API 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 this is not the case, how to dynamically allocate correctly aligned memory?

+3
c ++ memory-management memory-alignment
Oct 20 '14 at 17:44
source share
1 answer

I cannot find where the standard ensures that your proposed code will work. Firstly, I can’t find the part of the standard that supports what you quoted on CppReference.com, but even if we take this application on faith, it still only says that it can allocate additional space. If not, you are sunk.

The standard says about aligning the memory returned by operator new[] : "The returned pointer must be properly aligned so that it can be converted to a pointer to any complete object type ...". (C ++ 03, §3.7.2.1 / 2; C ++ 11, §3.7.4.1 / 2) However, in the context where you plan to allocate memory, the type that you plan to store in it is the full type. And besides, the result of operator new[] does not necessarily coincide with the result of the new expression new char[…] ; the latter is allowed to allocate additional space for its own accounting department of the array.

You can use C ++ 11 std::align . To ensure that you allocate space that can be aligned with the required amount, you will have to allocate object_size() + object_alignment() - 1 bytes, but in practice allocating only object_size() bytes is likely to be fine. So you can 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 dispenser described in another question does the same. It has been configured for the type of the selected object, to which you do not have access, but this is not required. The only time it uses its template type argument is to evaluate the sizeof and alignof that you already have from your object_size and object_alignment .

This is similar to what is required from the consumers of your library. It would be much more convenient for them if you would move the selection behind the API:

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

Be sure to move the kill by calling delete , not just the destructor.

This leads to the fact that any alignment issues disappear, because the only module that really needs to know this is the one that already knows everything else about the selected type.

+1
Oct 20 '14 at 17:51
source share



All Articles