Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 275
Kalrish Avatar asked Oct 20 '14 17:10

Kalrish


People also ask

Does array support dynamic memory allocation?

You can use dynamic memory allocation only for arrays that are local to the MATLAB Function block. You cannot use dynamic memory allocation for: Input and output signals. Variable-size input and output signals must have an upper bound.

Can we allocate memory for objects dynamically?

Also, it is a general query if we can allocate memory for the objects dynamically in C++? Yes, we can dynamically allocate objects also. Whenever a new object is created, constructor, a member function of a class is called. Whenever the object goes out of scope, destructor, a class member function is called.

What is aligned memory allocation?

The aligned_alloc function allocates space for an object whose alignment is specified by alignment, whose size is specified by size, and whose value is indeterminate.

Where does the memory for the dynamic array come from?

The dynamic memory requested by our program is allocated by the system from the memory heap.


1 Answers

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.

like image 51
Rob Kennedy Avatar answered Sep 28 '22 10:09

Rob Kennedy