警告:获取“结构详细信息”的打包成员的地址可能会导致未对齐的指针值 [-Waddress-of-packed-member]

qwe*_*rty 6 c struct gcc-warning gcc9 gcc11

       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));
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

警告:获取“结构详细信息”的打包成员的地址可能会导致未对齐的指针值 [-Waddress-of-packed-member]

GCC 在 >9 版本中给出此警告。如何在不使用 [-Wno-address-of-packed-member] 的情况下摆脱此警告

oro*_*uig 0

在您帖子的评论中,您可以解释为什么它会发出警告,以及为什么采用不结盟成员的指针可能是一个坏主意。问题不在于为指针分配一个未对齐的值(这似乎是 6.3.2.3 的第 5 条允许的而只是关于取消引用它。取消引用它似乎在某些体系结构上是明确允许的,尽管有一个性能损失(例如:在x86 上,当不使用 SSE load/store 时),但它可能会导致严重的问题,具体取决于其他体系结构上的类型和生成的程序集,如果在用户空间中,通常会出现总线错误,如果在内核中,则可能会出现故障空间。

不幸的是,可能要做类似的事情,周围有一些代码可以获取打包结构元素的指针,甚至可以毫不羞耻地取消引用它们: 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

我可以想象有人可能会遇到这样的情况:他们被别人的带有打包结构的代码所困(请注意,在库的头文件中包含打包结构会很糟糕,因为打包是对语言及其结果取决于编译器)。或者也许他们有一个函数可以保证正确处理未对齐的指针;由于 C 标准没有提供在代码中传达这一点的方法,因此人们必须依赖函数的文档(和/或实现)来解决这一问题。

现在您已经了解了它的问题所在,而且有时在不设置全局编译器选项的情况下在本地抑制该警告也很有用;这是以下问题的答案:

如何在不使用 [-Wno-address-of-packed-member] 的情况下摆脱此警告

您可以使用结构体的基地址并向其添加通过 获得的相关偏移量offsetof()。这需要一些转换,即使变成宏,它也不是特别干净,因为它需要几个参数:

#define member_nowarn(structType, elementType, structPtr, memberName) \
  ((elementType*)(((char*)(structPtr)) + offsetof(structType, memberName)))
Run Code Online (Sandbox Code Playgroud)

这可以简化,这样您就不必传递elementType,但宏的返回值将是 a ,void*这意味着当您错误地将其传递给需要指向除类型之外的其他内容的指针时,编译器警告可能会被抑制元素的memberName,所以我不推荐这个:

#define member_nowarn_unsafe(structType, structPtr, memberName) \
  ((void*)((((char*)(structPtr)) + offsetof(structType, memberName))))
Run Code Online (Sandbox Code Playgroud)

如果您的编译器支持非标准typeof()(例如:这是由stddef.hin提供gcc的),您可以简化宏,以便不必传递任何类型,返回指针的类型仍然正确,并且使用它的代码变得更简单且错误更少 -易于:

#define member_nowarn_nostd(structPtr, memberName) \
  ((typeof((structPtr)->memberName)*)(((char*)(structPtr)) + offsetof(typeof(*(structPtr)), memberName)))
Run Code Online (Sandbox Code Playgroud)

示例代码:

#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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这对于解决对齐可能错误的事实没有任何作用。它只是通过一系列转换操作来清洗地址,从而**隐藏**未定义的行为。 (2认同)