Search code examples
c++clangc++20

Using std::unique_ptr with aligned type results in compiler warning


clang issues a warning that I don't understand in this context. It appears that the pointer is aligned to 8-byte, but clang complaints about it being 4-byte aligned.

To fix it I can cast it to the same type as the pointer, which seems redundant.

What am I missing here? What should I read/understand and is there a way to fix it?

#include <memory>

typedef struct 
{
    int type;
} type_t __attribute__ ((aligned (8))); // 3rd party

void doNothing(type_t*) { }

int main() {
    auto up = std::unique_ptr<type_t>(nullptr);
    auto raw_ptr = up.get();
    static_assert(alignof(type_t*) == 8);
    static_assert(alignof(decltype(raw_ptr)) == 8);
    doNothing(raw_ptr); // compile error: passing 4-byte aligned argument to 8-byte aligned parameter
    doNothing(reinterpret_cast<type_t*>(raw_ptr)); // ok with cast
}

Solution

  • alignof(type_t*) == 8 is telling you that the alignment of the pointer type is aligned to 8 bytes (which is usually true on a 64 bit system), not that it points to something aligned to 8 bytes.

    With your program and Clang, alignof(type_t) == 8, but alignof(decltype(*raw_ptr)) == 4.

    There are two forms of __attribute__((aligned(N))). One on a struct:

    struct type_t __attribute__((aligned(8))) { int type; };
    struct type_t { int type; } __attribute__((aligned(8)));
    typedef struct { int type; } __attribute__((aligned(8))) type_t;
    

    And one on a type name:

    typedef int more_aligned_int __attribute__ ((aligned (8)));
    typedef struct type_t type_t __attribute__ ((aligned (8)));
    typedef struct { int type; } type_t __attribute__((aligned(8)));
    typedef struct type_t { int type; } type_t __attribute__((aligned(8)));
    

    The one on the struct physically changes the layout of the struct to be aligned to 8 bytes:

    typedef struct { int type; } __attribute__((aligned(8))) type_t;
    static_assert(sizeof(type_t) == 8);  // 4 padding bytes
    static_assert(alignof(type_t) == 8);
    

    The one on a typedef-name only changes the alignment when it is accessed through that name:

    typedef int more_aligned_int __attribute__ ((aligned (8)));
    typedef struct type_t { int type; } type_t __attribute__((aligned(8)));
    static_assert(sizeof(more_aligned_int) == 4);
    static_assert(alignof(more_aligned_int) == 8);
    static_assert(sizeof(type_t) == 4);  // no padding bytes
    static_assert(alignof(type_t) == 8);
    static_assert(sizeof(struct type_t) == 4);
    static_assert(alignof(struct type_t) == 4);  // Not 8: The struct's alignment did not change
    

    When you pass an aligned struct to a template parameter, nothing happens since the struct is laid out differently to be more aligned.
    However, when you pass a typedef name to templates, the alignment is taken out. std::vector<more_aligned_int> is exactly the same type as std::vector<int> (The aligned attribute is orthogonal to the type system, it's a property of the typedef).

    Clang removes the alignment from the typedef name, leading to alignof(std::unique_ptr<type_t>::value_type) == 4, since it's the alignment of the struct and not the typedef-name. So, .get() returns that not-aligned pointer, and raw_ptr has the type of a ptr without the alignment.

    If you specifically cast it back to the typedef name:

      type_t* raw_ptr = up.get();
    

    It will be aligned again.


    This might be a clang bug because it's different to what GCC does (which keeps the alignment even when passed as a templates argument). However, Clang's behaviour is more consistent.

    The attribute is in the wrong place to apply to the struct:

    typedef struct 
    {
        int type;
    } __attribute__ ((aligned (8))) type_t;
    
    // Now: sizeof(type_t) == 8, so you can have arrays of type_t.
    

    Tell that to your 3rd party library provider.