warning: taking address of packed member of 'struct details' may result in an unaligned pointer value [-Waddress-of-packed-member]
Asked Answered
H

1

8
       struct details_state {
               struct details_status D1;
               struct details_status D2;
       };

       struct details {
           struct details_state details_states[2];
       } __attribute__((packed));


        struct details *p;

        void print_details_status(struct details_status *s)

        print_details_status(&(p->details_states[i].D1));
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: taking address of packed member of 'struct details' may result in an unaligned pointer value [-Waddress-of-packed-member]

GCC gives this warning with >9 version. How to get rid of this warning without using [-Wno-address-of-packed-member]

Hyperthyroidism answered 22/2, 2022 at 8:17 Comment(11)
Why don't you want to use the option that's specifically intended to do what you want?Celeski
Do you want to get rid of the warning or do you want to ensure aligned addresses?Irreformable
BTW, The declaration of struct details_state needs to be before struct details.Celeski
I want to ensure aligned addresses if there's a problem with the way it is used right now.Hyperthyroidism
so remove __attribute__((packed)).Monoclinous
that is needed because of the way code is intended to work :-)Hyperthyroidism
You cannot have both (in this case, but the definition of struct details_status is missing): aligned addresses and a packed structure. You need to choose one of them.Brottman
You could make a copy using memcpy and pass the address of the copy.Irreformable
@Irreformable how would it matter? I could not understand.Hyperthyroidism
If you create a new variable, it will have proper alignment for the given type. memcpy will work without any alignment requirement. Therefore you can copy the mis-aligned memory area into well-alignemd memory area and then take the address for calling your function.Irreformable
What Gerhardh is saying is, that if you copy the value of an unaligned object into an aligned object, you can take the address of the latter without a warning.Brottman
B
0

In the comments to your post you have the explanation of the why it warns and why it can be a bad idea to take a pointer of a non-aligned member. The issue is not so much about assigning an unaligned value to a pointer (which seems to be permitted by bullet 5 of 6.3.2.3 here, but only about dereferencing it. Dereferencing it seems to be even explicitly permitted on some architectures, albeit with a performance penalty (e.g.: on x86 when not using SSE load/store), but it may cause severe issues depending on type and generated assembly on other architectures, typically a BUS ERROR if you are in user space or a fault if you are in kernel space.

For bad that it may be to do something like that, there is code around that takes pointers of packed struct's elements and even dereferences them without shame: https://github.com/abperiasamy/rtl8812AU_8821AU_linux/blob/4d1726146fd96552c9fa5af05c75187027d6885b/core/rtw_cmd.c#L1481 https://github.com/abperiasamy/rtl8812AU_8821AU_linux/blob/4d1726146fd96552c9fa5af05c75187027d6885b/core/rtw_mlme.c#L3689

I can imagine that someone may be in a situation where they are stuck with someone else's code with a packed struct (note that it would be bad to have a packed struct in a library's header file, as packing is a non-standard extension to the language and its results are compiler dependent). Or maybe they have a function that is guaranteed to deal properly with an unaligned pointer; as the C standard provides no way to communicate that in the code, one would have to rely on the function's documentation (and/or implementation) to figure that.

Now that you understand what's wrong with it but also that it can sometimes be useful to suppress that warning locally without setting a global compiler option; here's an answer to:

How to get rid of this warning without using [-Wno-address-of-packed-member]

You can use the base address to the struct and add to it the relevant offset, obtained via offsetof(). This requires some casting and, even turned into a macro, it is not particularly clean as it requires several arguments:

#define member_nowarn(structType, elementType, structPtr, memberName) \
  ((elementType*)(((char*)(structPtr)) + offsetof(structType, memberName)))

This can be simplified so you don't have to pass the elementType, but then the return value of the macro will be a void* which means compiler warnings may get suppressed when you mistakenly pass it to a function that expects a pointer to something else than the type of the memberName element, so I wouldn't recommend this:

#define member_nowarn_unsafe(structType, structPtr, memberName) \
  ((void*)((((char*)(structPtr)) + offsetof(structType, memberName))))

If your compiler supports the non-standard typeof() (e.g.: this is provided by stddef.h in gcc), you can simplify the macro so that no type has to be passed, the returning pointer's type is still correct and the code using it becomes simpler and less error-prone:

#define member_nowarn_nostd(structPtr, memberName) \
  ((typeof((structPtr)->memberName)*)(((char*)(structPtr)) + offsetof(typeof(*(structPtr)), memberName)))

Example code:

#include <stdio.h>
#include <stddef.h>
#include <string.h>

typedef float MYTYPE;
struct details {
        char mychar;
        MYTYPE mymember;
} __attribute__((packed));

struct details d;
void myfunc(MYTYPE*);
#define member_nowarn(structType, elementType, structPtr, memberName) \
  ((elementType*)(((char*)(structPtr)) + offsetof(structType, memberName)))
#define member_nowarn_unsafe(structType, structPtr, memberName) \
  ((void*)((((char*)(structPtr)) + offsetof(structType, memberName))))
#define member_nowarn_nostd(structPtr, memberName) \
  ((typeof((structPtr)->memberName)*)(((char*)(structPtr)) + offsetof(typeof(*(structPtr)), memberName)))

int main()
{
        d.mymember = 123;
        // warns
        myfunc(&d.mymember);
        //warns
        myfunc((MYTYPE*)&d.mymember);
        // ugly, but it does't warn
        myfunc(((MYTYPE*)((char*)(&d) + offsetof(struct details, mymember))));
        // same, turned into a macro
        myfunc(member_nowarn(struct details, MYTYPE, &d, mymember));
        // simpler, but the return value of the macro is void* so you won't get a
        // warning when passing to a function that requires a different type of pointer
        myfunc(member_nowarn_unsafe(struct details, &d, mymember));
        // simpler macro, but uses non-standard typeof()
        myfunc(member_nowarn_nostd(&d, mymember));
        return 0;
}

void myfunc(MYTYPE* arg)
{
        MYTYPE val;
        // do not dereference the pointer: it may crash
        // depending on MYTYPE, architecture and the generated 
        // assembly.
        // val = *arg; // don't do this
        // instead use memcpy to get the data it points to
        memcpy(&val, arg, sizeof(val));
        printf("%p %f\n", arg, (double)val);
}
Baring answered 24/1, 2023 at 14:16 Comment(4)
This does nothing to address the fact that the alignment can be wrong. It merely hides undefined behavior by laundering the address through a bunch of cast operations.Karsten
And is ((void*)(structPtr)) + offsetof(typeof(*(structPtr)) doing pointer arithmetic on a void * pointer to boot?!?!?!?!!!!Karsten
Exploiting that UB on x86 and the lucky subset of ARM users that have kernel fixups for unaligned loads, or exploiting that UB portably?Nembutal
@oromoiluig Corrected - I don't recall seeing a memcpy when I read the answer and didn't see an edit marker; either I saw an old copy or I somehow missed both of these. With that said, I'd also recommend expanding the remark about exactly the risks and possible outcomes - SIGBUS is a user-mode construct on Linux, but the code being used to justify and contextualize the action appears to be kernel-mode code which doesn't receive user-mode signals in that sense; you just get a fault (much like bare-metal).Nembutal

© 2022 - 2024 — McMap. All rights reserved.