【问题标题】:A bug in GCC implementation of bit-fields位域的 GCC 实现中的错误
【发布时间】:2012-07-02 05:55:44
【问题描述】:

在 C11 中工作,以下结构:

struct S {
  unsigned a : 4;
  _Bool    b : 1;
};

GCC 将其布局为 unsigned(4 字节),其中使用了 4 位,然后是 _Bool(4 字节),其中使用了 1 位,总大小为 8 字节。

请注意,C99 和 C11 明确允许 _Bool 作为位字段成员。 C11 标准(可能还有 C99)也在 §6.7.2.1 'Structure and union specifiers' ¶11 中声明:

实现可以分配任何大到足以容纳位字段的可寻址存储单元。如果有足够的空间,结构中紧跟在另一个位域之后的位域应该被打包到同一单元的相邻位中。

所以我认为上面的成员b应该已经打包到分配给成员a的存储单元中,导致结构体的总大小为4字节。

当对两个成员使用相同的类型时,或者当一个是unsigned 而另一个是signed 时,GCC 行为正确并且打包确实发生了,但是类型unsigned_Bool 似乎被认为太不同了由 GCC 正确处理。

谁能证实我对标准的解释,这确实是一个 GCC 错误?

我也对解决方法感兴趣(一些编译器开关、编译指示、__attribute__...)。

我正在使用带有-std=c11 的 gcc 4.7.0(尽管其他设置显示相同的行为。)

【问题讨论】:

  • 请注意,GCC 扩展 __attribute__ ((packed)) 可以应用于此处的成员,但与此问题正交(它导致大小为 4 + 1 = 5 的结构,即具有相同的问题。 )
  • 相关:stackoverflow.com/questions/308364/…(但指的是 C++,在位域的措辞上并不那么严格。)
  • 根据对上面链接的问题的回答,这种行为在 gcc 4.2.4 中没有发生,因此可能是此后的回归。
  • 经过进一步研究,看起来这种行为可能特定于 Windows 上的 GCC,因为它试图匹配 MSVC 的行为。仍然有兴趣解决此问题。

标签: gcc c99 bit-fields c11 compiler-bug


【解决方案1】:

所描述的行为与 C99 和 C11 标准不兼容,但提供了与 MSVC 编译器(具有不寻常的结构打包行为)的二进制兼容性。

幸运的是,可以在代码中将__attribute__((gcc_struct)) 应用于结构,或使用命令行开关-mno-ms-bitfields 禁用它(参见documentation)。

【讨论】:

  • 这是否记录在任何地方?我似乎在 mno-ms-bitfields 上找不到任何有用的东西
  • 谢谢,不知道为什么我以前找不到。将其编辑到您的答案中会很有用,因为 cmets 并不是长期的。
【解决方案2】:

在 64 位编译的 Mac OS X 10.7.4 上同时使用 GCC 4.7.1(自制)和 GCC 4.2.1(LLVM/clang†),此代码在 -std=c99 模式下生成 4

#include <stdio.h>

int main(void)
{
    struct S
    {
        unsigned a : 4;
        _Bool    b : 1;
    };
    printf("%zu\n", sizeof(struct S));
    return 0;
}

这是您在 Windows 上报告的大小的一半。它对我来说似乎大得惊人(我希望它的大小为 1 字节),但平台的规则就是它们。基本上,编译器没有义务遵循您想要的规则;它可能会遵循其运行平台的规则,并且在有机会的情况下,它甚至可以定义其运行平台的规则。

以下程序的行为有点可疑(因为它在最后一次写入u.s 之后访问u.i),但显示字段a 存储在4 个最低有效位中,而字段b 是存储在下一位:

#include <stdio.h>

int main(void)
{
    union
    {
        struct S
        {
            unsigned a : 4;
            _Bool    b : 1;
        } s;
        int i;
    } u;
    u.i = 0;
    u.s.a = 5;
    u.s.b = 1;
    printf("%zu\n", sizeof(struct S));
    printf("%zu\n", sizeof(u));
    printf("0x%08X\n", u.i);
    u.s.a = 0xC;
    u.s.b = 1;
    printf("0x%08X\n", u.i);
    return 0;
}

输出:

4
4
0x00000015
0x0000001C

† i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1(基于 Apple Inc. build 5658)(LLVM build 2336.9.00)

【讨论】:

    猜你喜欢
    • 2018-02-11
    • 1970-01-01
    • 2020-01-25
    • 1970-01-01
    • 2015-04-18
    • 1970-01-01
    • 1970-01-01
    • 2019-03-30
    • 1970-01-01
    相关资源
    最近更新 更多