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
}
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.