8

MSVC has its own non-standard functions _aligned_malloc, _aligned_realloc and _aligned_free.

C++17 and C11 have introduced (std::)aligned_alloc, results of which can be deallocated with free or realloc. But realloc cannot be used to actually reallocate memory returned by aligned_alloc, since it does not take an alignment parameter and thus cannot guarantee that the returned pointer will be properly aligned.

I can't even find any non-standard extensions that could reallocate aligned memory (preserving its alignment) on platforms other than Microsoft Windows / Visual C++.

Am I searching for it wrong, or is there indeed no _aligned_realloc alternative on POSIX and other platforms?

If so,

  1. Why?
  2. What can be used instead on those platforms? Is there nothing better than calling aligned_alloc with the new alignment, and then doing memcpy and freeing the old pointer on success?
Taras
  • 488
  • 3
  • 15
  • Only platforms/architectures that require alignment, require aligned alloc functions. Alignment is thus a function of the platform libraries and not a requirement of any standard. – Paul Ogilvie Jun 16 '19 at 14:17
  • 1
    "thus cannot guarantee that the returned pointer will be properly aligned." is unclear. All standard `*alloc()` functions provide "suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement". What/why alignment requirement above _fundamental alignment_ are you seeking? – chux - Reinstate Monica Jun 16 '19 at 14:20
  • 3
    @chux You sometimes need bigger than default alignment; when working with SIMD instructions for example. – Shawn Jun 16 '19 at 14:23
  • 1
    There are much fewer uses of realloc than malloc or free. There are much fewer uses of the aligned versions of malloc/free than the plain versions. An aligned version of realloc would be used very rarely and was probably not found to be worth the trouble. – Marc Glisse Jun 16 '19 at 17:34
  • @chux Depending on what that "fundamental alignment" means. If you say that I don't really need `aligned_realloc` at all, then why would they add `aligned_alloc` to the standard in the first place? And, also, isn't it a pessimization? The fact that the standard `realloc` would give me a (e.g.) 64-byte aligned storage (suitable for almost anything) while I need only 16-byte aligned storage? This seems to enforce some stricter requirements on the system than are really necessary. – Taras Jun 16 '19 at 21:18
  • @Shawn Yes, and sometimes I need less than the alignment of `std::max_align_t` (which is, if I understand correctly, that default alignment that good old `malloc` and `realloc` return). This is another reason why I would like to specify explicitly my alignment requirements to a "realloc" call. And that's why I don't understand why an initial allocation function (`std::aligned_alloc`) allows me to do that, but a reallocation function doesn't. It looks quite inconsistent for me right now. – Taras Jun 16 '19 at 21:26
  • @PaulOgilvie I cannot agree with that, because `std::aligned_storage` has been a part of C++ starting from 2011, and `(std::)aligned_alloc` was added to C in 2011 and to C++ in 2017. So, alignment is standard. And event if it wasn't, there should have been some non-standard extensions that would allow aligned allocations. Almost all platforms have had such things for a long time before they were standardized. But only allocating functions, not REallocating (except for Microsoft). Which is the thing that confuses me the most. – Taras Jun 16 '19 at 21:35
  • @Taras: "*The fact that the standard realloc would give me a (e.g.) 64-byte aligned storage (suitable for almost anything) while I need only 16-byte aligned storage?*" Do you believe that `aligned_alloc` *won't*? Because there's certainly no guarantee of that. `aligned_alloc` exists mainly for allocating objects with extended alignments, not for smaller alignments than the maximum fundamental alignment. – Nicol Bolas Jun 16 '19 at 22:45
  • @NicolBolas "*Do you believe that aligned_alloc won't?*" I'm not sure. Probably it will. In this case, let's focus on such extended alignments, which are greater than the maximum fundamental alignment. I still don't understand why they implemented `aligned_alloc` and did not provide `aligned_realloc` for reallocating memory while keeping the previous (extended) alignment (which I could have passed to `aligned_alloc` before). Without such `aligned_realloc`, I will have to do another allocation and `memcpy` data there if I want to make my storage "grow". Which might be suboptimal in some cases. – Taras Jun 16 '19 at 23:48
  • Your post says that `realloc` can be combined with `alligned_alloc`. This says otherwise: https://en.cppreference.com/w/c/memory/realloc. `Reallocates the given area of memory. It must be previously allocated by malloc(), calloc() or realloc() and not yet freed with a call to free or realloc. Otherwise, the results are undefined.` This is different from the description for `free` which says `Deallocates the space previously allocated by malloc(), calloc(), aligned_alloc, (since C11) or realloc()`. – fdk1342 Jun 17 '19 at 00:34
  • @Fred It seems I have misread that line here: https://en.cppreference.com/w/cpp/memory/c/aligned_alloc "*On success, returns the pointer to the beginning of newly allocated memory. To avoid a memory leak, the returned pointer must be deallocated with std::free() or std::realloc().*" It was about deallocating the returned pointer, not about REallocating it. I will fix the text in my post. – Taras Jun 17 '19 at 01:49
  • @Taras To answer Why? I think an `_aligned_realloc` shouldn't be needed. If it were me I would have made the standard `realloc` preserve the alignment restriction set by `aligned_alloc`. A variable moving alignment doesn't seem to be that useful (it makes more sense to me to just use the maximum alignment that would be required for whatever you are doing). I'd be curious what the standard actual says for `std::realloc`. If it truly is "undefined' that just means the implementation can choose to do whatever it wants, including preserving the alignment across use. – fdk1342 Jun 17 '19 at 12:59
  • @Fred I completely agree with you here: "*If it were me I would have made the standard realloc preserve the alignment restriction set by aligned_alloc.*" -- in my case, I don't need variable moving alignment as well; I only want to reallocate memory and preserve its previous alignment. But, alas, the standard `realloc` function does not guarantee that. – Taras Jun 17 '19 at 13:21
  • @fdk1342 That would require allocator to store additional information about the requested alignment and increase malloc memory overhead which is already large (2 pointers). –  Jan 06 '20 at 16:03

3 Answers3

7

While POSIX (which tends to act as a lowest common denominator on most platforms) does not have an aligned_realloc, it does have aligned_alloc and memcpy. Therefore you can very easily implement your own aligned_realloc which is guaranteed to work on any reasonably posix compliant platform using these. However, note that there is not a posix standard method to get the size of a malloc'd region of memory. You'll have to track that yourself.

EDIT: have a bit of free time so I'm extending this to answer the most common criticism

What I've proposed is, as the astute commenter will note, not how realloc works internally.

Under the hood, your standard realloc implementation will do its damnedest to avoid preforming the above behavior of mallocing and memcpying with a free afterwards. It will try to use one of two behaviors before resorting to the fallback. 1) If the new size is smaller than the old size, it will resize the memory in place, avoiding having to allocate, copy, or free. 2) if the new size is greater than the old size, it will (in simplified terms) see if there is free memory of sufficient size adjacent, and if so it will gobble up that memory and resize in place. If not, it resorts to the fallback.

I proposed a naive approach, because I figured most people asking this question wouldn't want to have to implement their own malloc implementation. (Though I highly suggest doing such for educational purposes)

Hope this satisfies any complaints!

  • 2
    The `realloc` function has a number of design defects. Adding a new variant would require either perpetuating those defects or reaching a consensus as to how they should be fixed. On many implementations, the most practical approach to fix most of these issues would be to pass a pointer to the pointer to the object, but some implementations would not be able to support such a concept. Consequently, it's unlikely that there could ever be a consensus solution. – supercat Jun 18 '19 at 20:54
  • I never mentioned that, though thanks for the fun bit of trivia. – Valyrie H. Autumn Jun 18 '19 at 21:31
4

Intel Math Kernel Library (free; available for Windows, Linux and macOS) ver. >= 11.3.1 has mkl_realloc that preserves alignment:

Simple example:

auto p1 = std::aligned_alloc(1024, 1000);
std::cout << reinterpret_cast<std::uintptr_t>(p1) % 1024 << std::endl;
auto p2 = std::realloc(p1, 2000);
std::cout << reinterpret_cast<std::uintptr_t>(p2) % 1024 << std::endl;
auto p3 = std::realloc(p2, 3000);
std::cout << reinterpret_cast<std::uintptr_t>(p3) % 1024 << std::endl;

auto q1 = mkl_malloc(1000, 1024);
std::cout << reinterpret_cast<std::uintptr_t>(q1) % 1024 << std::endl;
auto q2 = mkl_realloc(q1, 2000);
std::cout << reinterpret_cast<std::uintptr_t>(q2) % 1024 << std::endl;
auto q3 = mkl_realloc(q2, 3000);
std::cout << reinterpret_cast<std::uintptr_t>(q3) % 1024 << std::endl;

The output on my machine is:

0
784
784
0
0
0
Evg
  • 25,259
  • 5
  • 41
  • 83
0

Just a simple cross-post, from the following two, it should not be too hard to create an aligned_realloc function:

#include <stdlib.h>
#include <assert.h>

void* seriously_aligned_malloc(size_t alignment, size_t size) {
    assert(alignment<=255);
    char* allocated = (char*)malloc(size + alignment);
    unsigned char extraspace = (unsigned char)(alignment - (size_t)(allocated+alignment) % alignment);
    char* ptr = allocated + extraspace;
    unsigned char* extraspaceptr = (unsigned char*)(ptr - 1);
    *extraspaceptr = extraspace;
    return ptr;
}
void seriously_aligned_free(void* p) {
    char* ptr = (char*)p;
    unsigned char* extraspaceptr = (unsigned char*)(ptr - 1);
    unsigned char extraspace = *extraspaceptr;
    char* allocated = ptr - extraspace;
    free(allocated);
}

I believe there is also information at:

malat
  • 12,152
  • 13
  • 89
  • 158