Peeter Joot
Peeter Joot

Reputation: 8260

How to do c++ aligned array allocation?

I'd like to modify an array allocation:

 float * a = new float[n] ;

to use an aligned allocator. I was inclined to try to use placement new and posix_memalign (or the new c++11 equivalent), but see that placement new with arrays is problematic with array allocations, because the compiler may need to have additional storage for count or other metadata.

I tried:

int main()
{
   float * a = new alignas(16) float[3] ;

   a[2] = 0.0 ;

   return a[2] ;
}

but the compiler seems to indicate that the alignas is ignored:

$ g++ -std=c++11 t.cc -Werror
t.cc: In function ‘int main()’:
t.cc:4:39: error: attribute ignored [-Werror=attributes]
    float * a = new alignas(16) float[3] ;
                                       ^
t.cc:4:39: note: an attribute that appertains to a type-specifier is ignored

It looks like the proper way to use alignas is in a structure declaration declare a structure with alignas, but that will only work with a fixed size.

There is also a aligned_storage template, but I think that will also only work with fixed sizes.

Is there any standard way to do an aligned array allocation that will invoke the constructor on all the elements?

Upvotes: 10

Views: 16154

Answers (3)

Revolver_Ocelot
Revolver_Ocelot

Reputation: 8785

As other people said, overaligned types are not required to be supported. Check your compiler documentation before using it.

You can try to solve your problem using one of the following approaches:

1) Overallocate your array (by (desired aligment / sizeof element) - 1) and use std::align. A link to libstdc++ implementation.

2) declare a struct containing array of desired aligment / sizeof element elements and aligned by desired aligment. It should give you compact representation in memory if you use array of such structs, but you will not be able to use normal array notation or pointer arithmetics (as it (a) undefined behaviour, (b) there is a small chance that they will not be placed as you want)

3) Write your own aligned allocation function. Notice that you can add your own versions of operator new and delete.

namespace my
{
    struct aligned_allocator_tag {};
    aligned_allocator_tag aligned;
}

void* operator new( std::size_t count, my::aligned_allocator_tag, std::size_t aligment);
void* operator new[]( std::size_t count, my::aligned_allocator_tag, std::size_t aligment)
{
    return ::operator new(count, my::aligned, aligment);
}
//Usage
foo* c = new(my::aligned, 16) foo[20];

You will need to allocate memory, reserve enough space to store original pointer (returned by malloc/whatever) or amount of bytes pointer was displaced, so subsequent delete will free corect pointer, align pointer to needed size and return it.

Here is an answer, and another one, which shows how to align memory.

Notice that both of these answers uses implementation-defined behaviour of doing bitwise arithmetic on pointers converted to integers and converting them back. The only really completely standard way would be to cast memory to char* and add difference between its value and next aligned address.

If you can use some nonstandard memory allocation functions, you can wrap them into custom operator new too.

Upvotes: 5

Aiken Drum
Aiken Drum

Reputation: 593

Native support for alignment in C++ is still dismal. You're aligning to a 4-float vector, from the look of it, so you're missing the hazard where C++14 can't do better than 16 byte alignments, but even so, it's not a feature reliably supported by all C++14 compilers. If you need portable code that uses new float[], you've lost the race right out of the gate.

I think the closest you could get, within the current standard, would be to create a vector-sized and vector-aligned data type (e.g. with std::aligned_storage) and then get in the habit of using that, rather than arrays of individual floats, for your vector math. If you need variable-length vectors, then you'd have to round up to the nearest 4 floats.

(Sorry, it's not the answer you wanted, but I think it's the answer you need in order to move forward.)

Upvotes: 0

Barry
Barry

Reputation: 302817

Basically, you're stuck because, in [expr.new]:

It is implementation-defined whether over-aligned types are supported.

There is a proposal to support this better. Until then, if you want to do what you're trying to do, you'll have to use aligned_alloc instead of new.


If you stick your array in a struct:

struct S {
    alignas(16) float _[3];
};

then new S will give you the right alignment for _, though not necessarily for S itself. That may suffice. If it doesn't, then you can overload operator new() and operator delete() on S itself to guarantee the correct behavior.

Upvotes: 3

Related Questions