Search code examples
c++arraysalignmentnew-operatordelete-operator

Correctly deleting a dynamically allocated plain array, allocated with `std::align_val_t` alignment parameter


As an answer to Idiom for initializing an std::array using a generator function taking the index?, I proposed a way to obtain a properly aligned storage. Yet I think that my solution can be simplified, using this signature:

void* operator new[]( std::size_t count, std::align_val_t al );

Which I use for some type T like this:

T *p = new(std::align_val_t(alignof(T))) T[N];

Yet I cannot find the corresponding way to deallocate. The expected signature(s) may be:

void operator delete[]( void* ptr,std::align_val_t al ) noexcept;
void operator delete[]( void* ptr, std::size_t sz,std::align_val_t al ) noexcept;

But I couldn't find the proper way to call it. I tried, for instance:

    // sanitizer: new-delete-type-mismatch
    delete[] (N * sizeof(N), std::align_val_t{alignof(Int)}, p);
    // sanitizer: new-delete-type-mismatch
    delete[] (std::align_val_t{alignof(Int)}, p);
    //error: type 'enum class std::align_val_t' argument given to 'delete', expected pointer
    delete[] (p, N * sizeof(N), std::align_val_t{alignof(Int)});
    //error: type 'enum class std::align_val_t' argument given to 'delete', expected pointer
    delete[] (p, std::align_val_t{alignof(Int)});
    //error: type 'enum class std::align_val_t' argument given to 'delete', expected pointer
    // sanitizer attempting free on address which was not malloc()-ed
    ::operator delete[](p, std::align_val_t{alignof(Int)});
    // sanitizer attempting free on address which was not malloc()-ed
    ::operator delete[](p, N * sizeof(N), std::align_val_t{alignof(Int)});

but no version is compiling or accepted by the sanitizer. NB msvc fails to compile for all of them.

What is the correct way to use array version of new/delete with std::align_val_t?

NB How to call new[]/delete[] operator with aligned memory seems a dup but the error the op get is not the same and there don't seem to be a proper answer.
How to invoke aligned new/delete properly?, and especially this answer does not solve my issue (I tested the linked answer, which lead to sanitizer complaint)


Solution

  • There is no way to correctly deallocate that allocation.

    You are misusing the aligned operator new/operator delete feature.

    The way it works is that the new-expression and the delete-expression themselves will check the alignment requirement of the type that is to be created/destroyed and will call the std::align_val_t overloads of operator new/operator delete accordingly with correct values.

    So the correct use is

    T *p = new T[N];
    //...
    delete[] p;
    

    Both calls will insert an additional std::align_val_t(alignof(T)) argument to the list with which operator new[]/operator delete[] are called, before any placement-new argument you provide, if and only if T has a new-extended alignment, i.e. if alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__.

    As you noticed, there is no way to explicitly cause delete to use a specific std::align_val_t argument because it isn't possible to specify any additional arguments in a delete-expression at all.

    And calling operator delete[] yourself is also not possible, because the compiler is allowed to add an unspecified offset between the array and the allocation itself when using new for most types. You would need to know that offset to call operator delete[] with the correct pointer value.

    If you need larger alignment than alignof(T), then I would suggest wrapping in a type that provides the alignment requirement:

    struct alignas(/*...*/) A {
        T t[n];
    };
    
    A* a = new A;
    /*...*/
    delete a;
    

    Now again, the aligned allocation function mechanism will take care that a will be correctly aligned and because A is a standard-layout type you are then guaranteed that the first member a.t is also aligned to the same alignment. Or, if you like to be more explicit about what you want to have aligned:

    struct A {
        alignas(/*...*/) T t[n];
    };
    

    IMPORTANT: All of the above applies only since C++17. Before C++17 it was simply impossible to use new/delete without undefined behavior for overaligned types, except when overwriting operator new/operator delete manually to provide stronger alignment guarantees and even then only for non-arrays, because of the above-mentioned unspecified offset for array-news. Also, some compilers have options to disable the aligned allocation function mechanism. Make sure these are not set.