【问题标题】:What is the type of a bitfield?位域的类型是什么?
【发布时间】:2012-10-17 19:18:24
【问题描述】:

我在 C 标准中找不到任何指定的地方。例如,在

struct { signed int x:1; } foo;

foo.xint 类型的左值,还是其他?它是int 类型的左值似乎不自然,因为您不能在其中存储任何int 类型的值,只能存储0 或-1,但我找不到任何可以为其分配不同类型的语言。当然,在大多数表达式中使用,无论如何它都会被提升为int,但实际类型在C11中与_Generic有所不同,而且我在标准中找不到任何关于位域如何与@987654329交互的语言@ 要么。

【问题讨论】:

  • 位域的一个有趣之处在于intsigned int 在它们中具有不同的含义。不知道这是否有帮助。
  • 问题 2647320 中的“位字段的最大大小”与此问题没有直接关系。
  • 这个问题与位域值的大小或范围无关。我专门使用了signed 关键字来避免实现定义的签名问题。问题纯粹是关于左值foo.x具有什么类型的问题。

标签: c language-lawyer bit-fields c11


【解决方案1】:

正如 Jonathan 已经引用的那样,p5 清楚地说明了位域的类型。

您还应该记住的是,在 6.3.1.1 中有一个位域算术转换的特殊规则,基本上说明如果 int 可以表示所有值,例如位域 converts 在大多数表达式中为 int

_Generic 中的类型应该是声明的类型(以符号故障为模),因为似乎一致认为算术转换不适用,那里。所以

_Generic((X), int: toto, unsigned: tutu)
_Generic(+(X), int: toto, unsigned: tutu)

如果X 是一个无符号位字段,其宽度的所有值都适合int,则可能会给您不同的结果。

【讨论】:

    【解决方案2】:

    鉴于您包含了signed 限定符,那么可以存储在 1 位位字段中的唯一值确实是 -1 和 0。如果您省略了限定符,则将由实现定义是否'plain' int 位字段已签名或未签名。如果您指定了unsigned int,那么值当然是 0 和 +1。

    标准的相关部分是:

    §6.7.2.1 结构和联合说明符

    ¶4 指定位域宽度的表达式应为整数常量 具有不超过对象宽度的非负值的表达式 如果省略了冒号和表达式,将指定的类型。122) 如果值为 零,声明不应有声明符。

    ¶5 位字段的类型应为_Boolsigned intunsigned int 或其他一些实现定义的类型的合格或非合格版本。它是 实现定义是否允许原子类型。

    ¶10 位域被解释为有符号或无符号整数类型,包括 指定位数。125) 如果值 0 或 1 存储到 类型_Bool,位域的值应与存储的值相比较; _Bool 位域具有_Bool 的语义。

    122) 虽然_Bool 对象中的位数至少为CHAR_BIT,但宽度(符号数和 _Bool 的值位)可能只有 1 位。

    125) 如上面 6.7.2 所述,如果使用的实际类型说明符是 int 或定义为 int 的 typedef-name, 那么位域是有符号还是无符号由实现定义。

    脚注 125 指向:

    §6.7.2 类型说明符

    ¶5 每个逗号分隔的多重集指定相同的类型,除了位域, 说明符 int 是否指定与 signed int 或与unsigned int 相同的类型。

    【讨论】:

    • 我没有询问值的范围。我问左值有什么类型。这对于它如何与_Generic 交互以及以其他晦涩的方式(例如?: 操作员怪异)很重要。
    • 它的类型为signed int,不是吗-在您的示例中。如果您指定(纯)int,则类型将由实现定义。但类型是位域声明中指定的类型。 _Bool 如果您指定了 _Bool,否则,您指定的任何整数类型。我不确定是我遗漏了什么,还是你过度思考(或两者兼而有之)。
    • 我对你可以有一个int 类型的左值的概念有点困扰,它不能用于忠实地存储和检索任何int 类型的值。从概念上讲,似乎类型应该是“宽度为 1 的有符号位域”或类似的,但 C 没有这样的“类型”,所以我想左值的类型真的是 int...
    【解决方案3】:

    C11 规范当然没有明确说明这一点,而且可能存在缺陷。

    我相信foo.x 是一个类型不是int 的左值,但我的理由很不充分:

    6.2.7 第 1 段说:

    如果类型相同,则两种类型具有兼容类型

    6.3 第 2 段说:

    将操作数值转换为兼容类型不会导致值或表示形式发生变化。

    如果foo.xint 类型的左值,那么它将与其他ints 兼容,因此foo.x = 5 应该导致foo.x 具有值5(每6.3p2)。这显然不可能发生,表明foo.xint 不兼容,表明foo.x 不是int 类型的左值。

    foo.xint 不兼容是没有意义的。可能不会发生转换(在 6.3.1 意义上),并且 foo.x 通过标准中未讨论的某种机制获得其值。或者我可能误解了“算术操作数”的含义,并且 6.3.1 不适用于左值。

    还有 6.3.1.1 第 1 段第 2 条,它说:

    • 有符号整数类型的等级应大于任何精度较低的有符号整数类型的等级。

    foo.x 的精度低于普通的int(当用作左值时,而不是当它“转换为存储在指定对象中的值”时,如 6.3.2.1p2 中所述),因此它必须具有不同的整数转换等级。这也表明它不是int

    但我不确定我的解释是否有效或是否符合委员会的意图。

    我建议提交一份关于此的缺陷报告。

    【讨论】:

      【解决方案4】:

      C11 标准在 6.7.2.1p5 中声明:

      位域的类型应为 _Bool、signed int、unsigned int 或其他一些实现定义的类型的合格或非合格版本。

      这是一个约束,意思是,如果程序声明的位域的类型不属于上述类别之一,则必须打印诊断信息。

      但是在 6.7.2.1p10 中继续说:

      位域被解释为具有由指定位数组成的有符号或无符号整数类型。

      我相信他们的意思是,虽然你必须用 signed int x:n 之类的东西声明位字段,但左值表达式 foo.x 的类型是其他有符号整数类型,称之为 T。T 是有符号整数类型由 n 位组成,并且必须遵守 Sec.1 中给出的所有有符号整数类型的约束。 6.2.6.但 T 不一定与名为 signed int 的标准有符号整数类型兼容。 (事实上​​,T 可能与该标准类型兼容的唯一情况是,如果 n 恰好等于有符号 int 中的位数。)

      不幸的是,标准没有提供任何命名类型 T 的方法,所以我看不出它如何在 _Generic 中使用,至少不能以可移植的方式使用。 C 语言的特定实现可能会提供一种机制来命名这种类型,但标准并不强制它们这样做。

      【讨论】:

        【解决方案5】:

        ...实际类型在 C11 中与 _Generic 有所不同

        1编译器对位域类型的处理结果如下图。

        8 位和 32 位字段与通常的嫌疑人匹配。

        1 位位域的类型是什么?正如其他人所引用的,它的“名称”没有明确指定,但它不是任何预期的标准类型。

        这并没有引用规范,但确实展示了受人尊敬的编译器如何解释 C 规范。


        GNU C11 (GCC) 版本 5.3.0 (i686-pc-cygwin)
        由 GNU C 版本 5.3.0、GMP 版本 6.1.0、MPFR 版本 3.1.4、MPC 版本 1.0.3 编译

        #define info_typename(X) _Generic((X), \
          _Bool: "_Bool", \
          unsigned char: "unsigned char", \
          signed char: "signed char", \
          char: "char", \
          int: "int", \
          unsigned : "unsigned", \
          default: "default" \
          )
        
        #define TYPE_NAME(t) { printf("%s\n", info_typename(t)); }
        #include <stdio.h>
        
        int main() {
          struct {
            signed int x1 :1;
            signed int x8 :8;
            signed int x32 :32;
            _Bool b;
            signed char sc;
            char c;
            unsigned char uc;
            int i;
            unsigned u;
          } foo;
          TYPE_NAME(foo.b);
          TYPE_NAME(foo.sc);
          TYPE_NAME(foo.c);
          TYPE_NAME(foo.uc);
          TYPE_NAME(foo.i);
          TYPE_NAME(foo.u);
          TYPE_NAME(foo.x1);
          TYPE_NAME(foo.x8);
          TYPE_NAME(foo.x32);
        }
        

        输出

        _Bool
        signed char
        char
        unsigned char
        int
        unsigned
        default           (int:1)
        signed char       (int:8)
        int               (int:32) 
        

        注意-Wconversion 和以下代码,我收到此警告。所以这至少是一个编译器称之为它的小位字段类型:unsigned char:3

        警告:从“unsigned int”转换为“unsigned char:3”可能会改变其值 [-Wconversion]

          struct {
            unsigned int x3 :3;
          } foo;
          unsigned u = 1;
          foo.x3 = u; // warning
        

        【讨论】:

          【解决方案6】:

          位域的类型是:

          T 类型的位域

          其中T_Boolintsigned intunsigned int 或一些实现定义的类型。

          在您的示例中,foo.x 的类型为:signed int 类型的位域。

          这与signed int 不同,因为这两种类型不共享相同的约束和要求。

          例如:

          /* foo.x is of type bit-field of type signed int */
          struct { signed int x:1; } foo; 
          
          /* y is of type signed int */
          signed int y;                     
          
          /* valid, taking the size of an expression of type signed int */
          sizeof y;
          
          /* not valid, taking the size of an expression of type bit-field
           * of signed int */
          sizeof foo.x;  
          
          /* valid, taking the address of a lvalue of type signed int */
          &y;            
          
          /* invalid, taking the address of a lvalue of type bit-field
           * of signed int */
          &foo.x;        
          

          【讨论】:

          • 这是我愿意相信的,但我在标准中找不到任何定义“T 类型位域”之类的语言。事实上,&amp; 上的约束规范并没有说明表达式的 type;它们引用左值指定的 object,并要求它不是位域。
          • 更准确地说,它应该类似于 T 类型和宽度 n 的位字段。在 DR#120 open-std.org/jtc1/sc22/wg14/www/docs/dr_120.html 关于位域 C 委员会说这个声明 struct S { unsigned bit:1; }; bit 可以非正式地描述为 unsigned int : 1
          【解决方案7】:

          我怀疑这将取决于:

          1. 编译器
          2. 优化设置
          3. 位域中的最大位数

          【讨论】:

          • 对于给定的信息,编译器、优化设置或位字段中的最大位数没有任何内容。如果 signed 已被省略,那么它将具有“实现定义”的行为(因此它会因编译器而异),但优化级别不会是一个因素(除非实现定义其行为取决于优化级别,我假设,但这不太可能),最大位数仍然不是一个因素。
          • @Jonathan 你是对的。我相信我认为这类似于“枚举的基础类型是什么?”的情况
          猜你喜欢
          • 2016-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-22
          • 2010-12-24
          • 1970-01-01
          • 2015-05-19
          • 1970-01-01
          相关资源
          最近更新 更多