4
       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]

qwerty
  • 85
  • 2
  • 6
  • Why don't you want to use the option that's specifically intended to do what you want? – Barmar Feb 22 '22 at 08:24
  • Do you want to get rid of the warning or do you want to ensure aligned addresses? – Gerhardh Feb 22 '22 at 08:25
  • BTW, The declaration of `struct details_state` needs to be before `struct details`. – Barmar Feb 22 '22 at 08:26
  • I want to ensure aligned addresses if there's a problem with the way it is used right now. – qwerty Feb 22 '22 at 08:27
  • 2
    so remove `__attribute__((packed))`. – KamilCuk Feb 22 '22 at 08:37
  • that is needed because of the way code is intended to work :-) – qwerty Feb 22 '22 at 08:54
  • 1
    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. – the busybee Feb 22 '22 at 09:28
  • You could make a copy using `memcpy` and pass the address of the copy. – Gerhardh Feb 22 '22 at 09:30
  • @Gerhardh how would it matter? I could not understand. – qwerty Feb 22 '22 at 09:40
  • 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. – Gerhardh Feb 22 '22 at 09:49
  • 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. – the busybee Feb 22 '22 at 11:05

1 Answers1

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);
}
oromoiluig
  • 126
  • 1
  • 9
  • 2
    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. – Andrew Henle Jan 24 '23 at 15:36
  • 1
    And is `((void*)(structPtr)) + offsetof(typeof(*(structPtr))` doing pointer arithmetic on a `void *` pointer to boot?!?!?!?!!!! – Andrew Henle Jan 24 '23 at 15:37
  • Exploiting that UB on x86 and the lucky subset of ARM users that have kernel fixups for unaligned loads, or exploiting that UB portably? – nanofarad Jan 24 '23 at 23:58
  • @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). – nanofarad Jan 25 '23 at 04:28