GCC实现的位域存在漏洞

9
在C11中工作时,下面的结构体:
struct S {
  unsigned a : 4;
  _Bool    b : 1;
};

由GCC布置为一个无符号的(4字节),其中使用了4位,后跟一个_Bool (4字节),其中使用了1位,总大小为8字节。
请注意,C99和C11特别允许_Bool作为位字段成员。 C11标准(可能也是C99)还在§6.7.2.1“结构和联合说明符” ¶11下规定:
实现可以分配任何足够大以容纳位字段的可寻址存储单元。 如果有足够的空间,则立即在结构中紧随另一个位字段后面的位字段将被打包到同一单元的相邻位中。
因此,我相信上面的成员b应该已经被打包到为成员a分配的存储单元中,导致总大小为4个字节的结构体。
当使用两个成员相同的类型或一个是unsigned而另一个是signed时,GCC会正确地进行打包,但是类型unsigned和_Bool似乎对于GCC来说过于不同以处理它们正确。
有人可以确认我对标准的解释是否正确,并且这确实是一个GCC错误吗?
我也对解决方法感兴趣(某些编译器开关,pragma,__attribute__ ...)。
我正在使用gcc 4.7.0和-std=c11(尽管其他设置显示相同的行为)。

请注意,GCC扩展__attribute__ ((packed))可以应用于这里的成员,但与此问题无关(它会导致一个大小为4 + 1 = 5的结构体,即具有相同的问题)。 - ndkrempel
相关:https://dev59.com/0XVC5IYBdhLWcg3wZwJT(但是涉及到C ++,其措辞上不是非常严格关于位域的。) - ndkrempel
根据上面链接的问题的答案,这种行为在gcc 4.2.4中没有发生过,因此可能是自那时以来的退化。 - ndkrempel
经过进一步的研究,看起来这种行为可能是特定于 Windows 上的 GCC,因为它试图匹配 MSVC 的行为。仍然有兴趣找到禁用此功能的解决方法。 - ndkrempel
2个回答

10
描述的行为与C99和C11标准不兼容,但是为了与MSVC编译器(其具有不寻常的结构体打包行为)二进制兼容而提供。
幸运的是,可以通过在结构体上应用__attribute__((gcc_struct))或使用命令行开关-mno-ms-bitfields(请参阅documentation)来禁用它。

这个有文档吗?我似乎找不到关于 mno-ms-bitfields 有用的信息。 - Shafik Yaghmour
谢谢,不确定为什么我之前找不到那个。将其编辑到您的答案中会很有用,因为评论不应该是长期的。 - Shafik Yaghmour
应该在GCC >= 8中修复。请参见https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991。 - benjarobin

0

在 Mac OS X 10.7.4 上使用 GCC 4.7.1(自行构建)和 GCC 4.2.1(LLVM/clang†),进行64位编译,该代码在 -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(基于苹果公司构建的版本5658)(LLVM构建2336.9.00)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接