Std :: align and std :: aligned_storage for aligned memory allocation

I am trying to allocate a memory block of size size , which must be aligned by Alignment , where the size cannot be determined at compile time. I know that routines such as _aligned_alloc , posix_memalign , _mm_alloc , etc. _mm_alloc , but I don’t want to use them because they reduce code portability.
C ++ 11 provides the std::align routine, as well as the std::aligned_storage , from which I can get the POD type to select the element that will be aligned according to my requirements. However, my goal is to create a allocator that allocates a size block of memory (and not just one element) that is aligned.
Is this possible with std::align ? The reason I ask is because std::align moves the pointer, the class using this pointer will give the allocator a pointer to the moved address to release, which will be invalid. Is there a way to create aligned_allocator this way?

+7
c ++ alignment c ++ 11 allocator
Jun 29 '13 at 8:52
source share
2 answers

EDIT: after clarification from the OP, the original answer seems to be off topic; for reference, it is at the end of this answer.

Actually, the answer is quite simple: you just need to save a pointer to both the storage unit and the first element.

In fact, this does not require a generator with installed states (this is possible even in C ++ 03, although with the user-defined subroutine std::align ). The trick is that the distributor does not need to request enough memory from the system to store user data. He may well ask a little more about accounting.

So here we create a aligned dispenser; to keep it simple, I will focus on distribution / release routines.

 template <typename T> class aligned_allocator { // Allocates block of memory: // - (opt) padding // - offset: ptrdiff_t // - T * n: T // - (opt) padding public: typedef T* pointer; typedef size_t size_type; pointer allocate(size_type n); void deallocate(pointer p, size_type n); }; // class aligned_allocator 

And now the distribution procedure. A lot of memory is busy, this is the heart of the dispenser after all!

 template <typename T> auto aligned_allocator<T>::allocate(size_type n) -> pointer { size_type const alignment = std::max(alignof(ptrdiff_t), alignof(T)); size_type const object_size = sizeof(ptrdiff_t) + sizeof(T)*n; size_type const buffer_size = object_size + alignment; // block is correctly aligned for `ptrdiff_t` because `std::malloc` returns // memory correctly aligned for all built-ins types. void* const block = std::malloc(buffer_size); if (block == nullptr) { throw std::bad_alloc{}; } // find the start of the body by suitably aligning memory, // note that we reserve sufficient space for the header beforehand void* storage = reinterpret_cast<char*>(block) + sizeof(ptrdiff_t); size_t shift = buffer_size; void* const body = std::align(alignment, object_size, storage, shift); // reverse track to find where the offset field starts char* const offset = reinterpret_cast<char*>(body) - sizeof(ptrdiff_t); // store the value of the offset (ie, the result of body - block) *reinterpret_cast<ptrdiff_t*>(offset) = sizeof(ptrdiff_t) + shift; // finally return the start of the body return reinterpret_cast<ptrdiff_t>(body); } // aligned_allocator<T>::allocate 

Fortunately, the redistribution procedure is much simpler, you just need to read the offset and apply it.

 template <typename T> void aligned_allocator<T>::deallocate(pointer p, size_type) { // find the offset field char const* header = reinterpret_cast<char*>(p) - sizeof(ptrdiff_t); // read its value ptrdiff_t const offset = *reinterpret_cast<ptrdiff_t*>(header); // apply it to find start of block void* const block = reinterpret_cast<char*>(p) - offset; // finally deallocate std::free(block); } // aligned_allocator<T>::deallocate 

Other routines do not need to know about the memory layout, so writing them is trivial.




Original answer:

 template <typename T> class Block { public: Block(Block const&) = delete; Block& operator=(Block const&) = delete; explicit Block(size_t n); ~Block(); private: void* _storage; T* _begin; T* _end; }; // class Block template <typename T> Block<T>::Block(size_t n) { size_t const object_size = n * sizeof(T); size_t const buffer_size = object_size + alignof(T); _storage = std::malloc(size); void* stock = _storage; size_t shift = buffer_size; std::align(alignof(T), object_size, stock, shift); _begin = _end = reinterpret_cast<T*>(stock); } // Block<T>::Block template <typename T> Block<T>::~Block() { for (; _end != _begin; --_end) { (_end - 1)->~T(); } std::free(_storage); } // Block<T>::~Block 
+3
Jun 29. '13 at 13:53 on
source share

If this should be a C ++ 11 solution, then ignore this answer.

If not ... I don't know if you know this, but here is one option:

 void * aligned_malloc( size_t size, size_t alignement ) { void * p = malloc( size + --alignement ); void * p1 = (void*)( ( (size_t)p + alignement ) & ~alignement ); ((char*)p1)[ -1 ] = (char)((char*)p1 - (char*)p); return p1; } void aligned_free( void * pMem ) { char * pDelete = (char*)pMem - ((char*)pMem)[ -1 ]; free( pDelete ); } 

Maybe malloc and free are not 100% portable, but they can easily deal with such cases using preprocessor directives.

+1
Jun 29 '13 at 13:40
source share



All Articles