Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading operator new with smaller default alignment

C++17 introduced Dynamic memory allocation for over-aligned data

Beside the existing std::max_align_t, the fundamental alignment, it added __STDCPP_DEFAULT_NEW_ALIGNMENT__ the minimal alignment that the operator new guarantees.

With MSVC2017 64bit compilation, these constants result in a std::max_align_t of size 8 and __STDCPP_DEFAULT_NEW_ALIGNMENT__ of size 16.

It is however allowed to overrule the operator new/free, as mentioned on cppreference: operator new - global replacements.

Looking at all of the documents, it's unclear to me if this function is allowed to provide a new default alignment and if so, if we are allowed to redefine this constant.

An example for illustration:

#include <new>
#include <iostream>
#include <cassert>
#include <cstdint>
#include <cstddef>

static_assert(alignof(std::max_align_t) == 8);
static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ == 16);


void * operator new(size_t size) 
{ 
    std::cout << "New operator overloading " << std::endl; 
    void * p = std::malloc((size == 8) ? 16 : size); 
    assert(std::uintptr_t(p)%16 == 0);
    if (size == 8)
        {
        auto correctedPtr = std::uintptr_t(p) + 8;
        return (void*)correctedPtr;
        }
    return p; 
} 

void operator delete(void * p) 
{ 
    std::cout << "Delete operator overloading " << std::endl; 
    if (std::uintptr_t(p)%16 != 0)
    {
        auto correctedPtr = std::uintptr_t(p) - 8;
        std::free((void*)correctedPtr);
    }
    std::free(p); 
}

namespace
{
    struct D
    {
        double d;
    };
}


int main(int, char**)
{
    new D{};
    return 0;
}

Code at compiler explorer

The reason I'm asking this, is because I'm investigating crashes in an MSVC program that is now being compiled with Clang. Here we noticed that clang uses CPU instructions that rely on this 16 bit alignment in order to initialize a class of size 8.

like image 237
JVApen Avatar asked Jun 22 '19 08:06

JVApen


1 Answers

According to N4659 (last public draft for C++17):

6.7.4p3:

Any allocation and/or deallocation functions defined in a C++program, including the default versions in the library, shall conform to the semantics specified in 6.7.4.1 and 6.7.4.2.

6.7.4.1p2:

... The pointer returned shall be suitably aligned so that it can be converted to a pointer to any suitable complete object type (21.6.2.1) and then used to access the object or array in the storage allocated(until the storage is explicitly deallocated by a call to a corresponding deallocation function). ...

19.8p1:

The following macro names shall be defined by the implementation: ... __STDCPP_DEFAULT_NEW_ALIGNMENT__ An integer literal of type std::size_t whose value is the alignment guaranteed by a call to operator new(std::size_t) or operator new[](std::size_t). ...

19.8p4:

If any of the pre-defined macro names in this subclause, or the identifier defined, is the subject of a #define or a #undef preprocessing directive, the behavior is undefined. ...

So, you cannot change the __STDCPP_DEFAULT_NEW_ALIGNMENT__ value inside your program, and if your allocation function is called for alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) type of size 8, you cannot detect that, but you still need to return a suitably aligned pointer.

Nonetheless, you can change the __STDCPP_DEFAULT_NEW_ALIGNMENT__ value as defined by clang itself using -fnew-alignment compiler option. Not sure if it helps in your case.

like image 187
Kit. Avatar answered Nov 03 '22 01:11

Kit.