I have the following struct:
typedef struct __attribute__ ((__packed__))
{
uint16_t a;
uint32_t b;
} st_t;
-> the "b" member is unaligned.
When I do the following, gcc raises a warning:
st_t st;
uint32_t * b_p = &st.b;
*b_p = 0;
warning log:
taking address of packed member of 'struct <anonymous>' may result in an unaligned pointer value
But when I do the following, It does not raise any warning:
st_t st;
st_t * st_p = &st;
st_p->b = 0;
I don't understand why it does not raise a warning in the second case, since I am still accessing an unaligned member.
CodePudding user response:
Can't provide a standard quote, so someone else will surely write a better answer, but here's a short one.
For a packed struct, from the very definition of what a "packed struct" is, compiler has to generate working machine code for any member access. Unaligned access is known and will be handled appropriately for the CPU.
Pointer to int, however, is assumed to point to a valid int, which may mean it has to be aligned (or there will be "bus error" or some such). Compiler generating valid unaligned access machine code for every int pointer dereference would be quite inefficient on CPUs where it matters, so compiler basically has to make this assumption.
So, if compiler notices a pointer to unaligned address, it rightfully warns, because it is illegal operation on some CPUs, and it may be slower than aligned access even if it is legal. It won't warn for packed struct, because programmer explocitly says "this is unaligned, deal with it" by making the struct packed.
CodePudding user response:
This is a badly designed warning from GCC.
When you apply the packed
attribute to a structure, it makes the alignment requirement of its members one byte. Thus, when you take the address of a uint32_t
member, what you get is not a uint32_t *
but a uint32_t __attribute__ ((__aligned__(1))) *
.1 It would be valid to assign this address to a uint32_t __attribute__ ((__aligned__(1))) *
or (with a cast) to an unsigned char *
.
Thus, ideally, the compiler would not warn you when you take the address of the member but rather when you use it in a way that requires greater alignment than it is guaranteed to have. Implementing such a warning may have been difficult at the time support for packed structures was created and this warning was added.
In this code:
st_t st;
st_t * st_p = &st;
st_p->b = 0;
the address of an st_t
is taken and is assigned to an st_t *
, so there is no problem. Then st_p->b
accesses the member b
but does not take its address, so there is no hazard that an unaligned address may be used as a pointer to an aligned address, so no warning is needed.
Footnote
1 GCC’s aligned
attribute only allows increasing the alignment requirement of a type, not decreasing it. packed
can decrease an alignment requirement, but GCC does not accept it on a plain uint32_t
. So the __aligned__(1)
notation is used in this answer to convey the intent of a uint32_t
with a one-byte alignment requirement, in spite of the fact there appears to be no way to specify this in GCC.