为什么给位字段赋值不返回相同的值?

Why is assigning a value to a bit field not giving the same value back?

我在 this Quora post 中看到了下面的代码:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = 1;
  if(s.enabled == 1)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

在 C 和 C++ 中,代码的输出是 意外的

Is disabled !!

虽然那个"sign bit"里面给出了相关的解释post,但我无法理解,怎么可能我们设置了一些东西,然后它并没有按原样反映出来。

谁能给出更详细的解释?


注意: 两个标签 & are required, because their standards slightly differ for describing the bit-fields. See answers for and .

I am unable to understand, how is it possible that we set something and then it doesn't show up as it is.

你是在问为什么它编译却给你一个错误?

是的,理想情况下它应该给您一个错误。如果您使用编译器的警告,它确实如此。在 GCC 中,使用 -Werror -Wall -pedantic:

main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1' 
changes value from '1' to '-1' [-Werror=overflow]
   s.enabled = 1;
           ^

为什么将其留给实现定义而不是错误的原因可能更多地与历史用法有关,在这种情况下需要强制转换意味着破坏旧代码。该标准的作者可能认为警告足以弥补相关人员的不足。

为了引入一些规范主义,我将附和@Lundin 的声明:"Never use bit-fields for any purpose."你的内存布局细节会让你认为你首先需要位域,你几乎肯定有的其他相关要求将 运行 反对他们的规格不足。

(TL;DR - 如果您足够成熟,可以合法地 "need" 位域,那么它们的定义不够明确,无法为您服务。)

标准对位域的定义非常糟糕。鉴于此代码struct mystruct {int enabled:1;};,那么我们不知道

  • 这占用多少 space - 如果有填充 bits/bytes 以及它们在内存中的位置。
  • 位在内存中的位置。未定义,也取决于字节顺序。
  • int:n 位域是否被视为有符号或无符号。

关于最后一部分,C17 6.7.2.1/10 说:

A bit-field is interpreted as having a signed or unsigned integer type consisting of the specified number of bits 125)

解释上述内容的非规范性注释:

125) As specified in 6.7.2 above, if the actual type specifier used is int or a typedef-name defined as int, then it is implementation-defined whether the bit-field is signed or unsigned.

万一这个位域要当成signed int,你弄了个位1,那就没地方放数据了,只放符号位。这就是为什么您的程序可能会在某些编译器上给出奇怪结果的原因。

良好做法:

  • 切勿将位域用于任何目的。
  • 避免使用带符号的 int 类型进行任何形式的位操作。

这是实现定义的行为。我假设你是 运行 这台机器使用 twos-compliment signed integers 并在这种情况下将 int 视为有符号整数来解释为什么你不输入 if 的 true 部分if 语句。

struct mystruct { int enabled:1; };

enable 声明为 1 位位域。由于它已签名,因此有效值为 -10。将字段设置为 1 会使该位溢出回到 -1(这是未定义的行为)

本质上,在处理带符号的位域时,最大值是 2^(bits - 1) - 1,在这种情况下是 0

你可以把它想象成2的补码系统,最左边的位是符号位。因此,任何设置了最左边位的有符号整数都是负值。

如果你有一个 1 位有符号整数,它只有符号位。因此,将 1 分配给该单个位只能设置符号位。因此,当回读时,该值被解释为负数,-1 也是如此。

一个 1 位有符号整数可以容纳的值是 -2^(n-1)= -2^(1-1)= -2^0= -12^n-1= 2^1-1=0

根据 C++ standard n4713,提供了一个非常相似的代码片段。使用的类型是BOOL(自定义),但它可以适用于任何类型。

12.2.4

4 If the value true or false is stored into a bit-field of type bool of any size (including a one bit bit-field), the original bool value and the value of the bit-field shall compare equal. If the value of an enumerator is stored into a bit-field of the same enumeration type and the number of bits in the bit-field is large enough to hold all the values of that enumeration type (10.2), the original enumerator value and the value of the bit-field shall compare equal. [ Example:

enum BOOL { FALSE=0, TRUE=1 };
struct A {
  BOOL b:1;
};
A a;
void f() {
  a.b = TRUE;
  if (a.b == TRUE)    // yields true
    { /* ... */ }
}

— end example ]


乍一看,粗体部分似乎可以解释。但是,当 enum BOOL 派生自 int.

时,正确的意图就变得清晰了
enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = TRUE;
  if(s.enabled == TRUE)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

使用上面的代码,它会在没有 -Wall -pedantic:

的情况下发出警告

warning: ‘mystruct::enabled’ is too small to hold all values of ‘enum BOOL’ struct mystruct { BOOL enabled:1; };

输出为:

Is disabled !! (when using enum BOOL : int)

如果将 enum BOOL : int 简化为 enum BOOL,则输出如上述标准段落指定:

Is enabled (when using enum BOOL)


因此,可以得出结论,正如其他一些答案所得出的那样,int 类型不足以将值“1”存储在一个位域中。

我看你对位域的理解没有问题。我看到的是您首先将 mystruct 重新定义为 struct mystruct { int enabled:1; } 然后作为 struct mystruct s;。你应该编码的是:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
    mystruct s; <-- Get rid of "struct" type declaration
    s.enabled = 1;
    if(s.enabled == 1)
        printf("Is enabled\n"); // --> we think this to be printed
    else
        printf("Is disabled !!\n");
}