【问题标题】:Bit-field types in GCC vs C11 standardGCC 与 C11 标准中的位域类型
【发布时间】:2019-09-26 08:09:43
【问题描述】:

根据 C11 标准(在 this answer 中提到),该标准强制支持以下类型:_Boolsigned intunsigned int。可以支持其他类型,但取决于实现。

我尝试按照代码查看实际中位域的类型:

#include <stdint.h>
#include <assert.h>
#include <stdio.h>

#define ARG_TYPE(arg)     _Generic((arg),               \
                                _Bool          : "_Bool", \
                                char           : "char",      \
                                signed char    : "signed char",    \
                                unsigned char  : "unsigned char", \
                                short          : "short", \
                                unsigned short : "unsigned short", \
                                int            : "int", \
                                unsigned int   : "unsigned int", \
                                long           : "long", \
                                unsigned long  : "unsigned long", \
                                long long      : "long long", \
                                unsigned long long : "unsigned long long")
int main(void)
{
    struct _s
    {
        unsigned int        uval32 : 32;
        unsigned int        uval16 : 16;
        unsigned int        uval8  : 8;
        unsigned int        uval1  : 1; 
        signed int          ival32 : 32;
        signed int          ival16 : 16;
        signed int          ival8  : 8;
        signed int          ival1  : 1;
        _Bool               bool1  : 1;
    } s = {0};

    printf("The type of s.uval32 is %s\n", ARG_TYPE(s.uval32));
    printf("The type of s.uval16 is %s\n", ARG_TYPE(s.uval16));
    printf("The type of s.uval8 is %s\n", ARG_TYPE(s.uval8));
    printf("The type of s.uval1 is %s\n", ARG_TYPE(s.uval1));
    printf("The type of s.ival32 is %s\n", ARG_TYPE(s.ival32));
    printf("The type of s.ival16 is %s\n", ARG_TYPE(s.ival16));
    printf("The type of s.ival8 is %s\n", ARG_TYPE(s.ival8));
    printf("The type of s.ival1 is %s\n", ARG_TYPE(s.ival1));
    printf("The type of s.bool1 is %s\n", ARG_TYPE(s.bool1));

    (void)s;

    return 0;
}

Clang (https://godbolt.org/z/fjVRwI) 和 ICC (https://godbolt.org/z/yC_U8C) 的行为符合预期:

The type of s.uval32 is unsigned int
The type of s.uval16 is unsigned int
The type of s.uval8 is unsigned int
The type of s.uval1 is unsigned int
The type of s.ival32 is int
The type of s.ival16 is int
The type of s.ival8 is int
The type of s.ival1 is int
The type of s.bool1 is _Bool

但是 GCC (https://godbolt.org/z/FS89_b) 引入了几个问题:

  1. _Bool 以外定义的单个位域不适合 _Generic 中引入的任何类型:

错误:“unsigned char:1”类型的“_Generic”选择器不兼容 与任何关联

  1. 在注释掉发出错误的行后,我得到了这个:

    The type of s.uval32 is unsigned int
    The type of s.uval16 is unsigned short
    The type of s.uval8 is unsigned char
    The type of s.ival32 is int
    The type of s.ival16 is short
    The type of s.ival8 is signed char
    The type of s.bool1 is _Bool
    

    对我来说,unsigned shortshortunsigned charsigned char 在这里完全出乎意料。

我误解了标准吗?这是 GCC 错误吗?

看起来像使用_Generic 即使对于定义明确的东西也不能移植......

【问题讨论】:

    标签: c gcc types bit-fields c11


    【解决方案1】:

    如上所述,没有编译器必须提供对古怪位字段类型的支持。如果是这样,则可以随意处理此类类型 - 这超出了标准的范围。您本质上是在谈论标准称为“存储单元”的抽象项目的类型。

    关于这个神奇的抽象“存储单元”的所有内容都是不明确的行为:

    C17 §6.7.2.1/11:

    一个实现可以分配任何大到足以容纳位域的可寻址存储单元。 如果有足够的空间,一个位域紧跟在另一个位域之后 结构应打包到同一单元的相邻位中。如果剩余空间不足, 不适合的位域是否放入下一个单元或与相邻单元重叠是 实现定义。一个单元内位域的分配顺序(高位到 低阶或低阶到高阶)是实现定义的。对齐方式 未指定可寻址存储单元。

    永远不要在任何地方使用位域,所有这些问题都会消失。无论如何都没有理由使用它们 - 这是一个 100% 多余的功能。

    【讨论】:

    • 我认为这与存储单元无关。 _Generic 是关于类型,而不是关于表示。但我完全同意你的最后一段:)
    • @JensGustedt 问题是,如果您有一个uint8_t 的位字段后跟uint16_t 之一,则无法确定各个字节在存储单元中的最终位置。存储单元至少应该与对齐有某种关系。我的看法是,clang 和 icc 将存储单元视为最小的对齐类型(无符号整数),然后将各种单独的位域成员显示到该类型中。
    【解决方案2】:

    是的,clang 在这里是正确的,而 gcc 是完全错误的。 位域的类型是定义的类型。时期。 标准中对此没有歧义,并且 gcc 的“功能”将它们作为包含指定位数的特定类型是不符合标准的。 有一个很长的讨论开始于

    https://gcc.gnu.org/ml/gcc/2016-02/msg00255.html

    这基本上表明他们不愿意让步并转向更人性化的模式。

    如果您真的对此的实际方面感兴趣,您可以只使用一种强制评估的方法,例如+ 或使用逗号运算符。这将失去_Boolint 位域之间的区别,但仍然可以让您区分longint

    【讨论】:

    • 我知道+ 选项,但如果arg 不一定是算术类型,那么它就无济于事......
    • 标准说“位域被解释为具有由指定位数组成的有符号或无符号整数类型。”,所以我看不到宽度为 3 的位域(例如) 应该匹配通用选择器中的unsigned int
    • @AlexLop。在这种情况下,您可以在控制表达式中使用逗号运算符做一些奇怪的事情,例如 (0, X)
    • @M.M 是的。它说它被解释为具有指定的位数。在关于位域及其存储方式的冗长讨论中,没有任何地方说类型与声明的类型不同。
    • @JensGustedt 它不适用于任何编译器(GCC、ICC、CLANG):godbolt.org/z/a43X9S 标准还说:“通用选择的控制表达式不是评估"
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-29
    • 2015-05-11
    • 1970-01-01
    • 2015-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多