97

我在这篇 Quora 帖子中看到了以下代码:

#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++ 中,代码的输出都是出乎意料的,

被禁用 !!

尽管在那篇文章中给出了与“符号位”相关的解释,但我无法理解,我们怎么可能设置了一些东西,然后它就不能按原样反映。

有人可以给出更详细的解释吗?


注意:标签都是必需的,因为它们的描述位域的标准略有不同。请参阅C 规范C++ 规范的答案。

4

6 回答 6

77

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

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

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

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

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

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

如果位域被视为signed int并且你做了一点 size 1,那么就没有数据空间了,只有符号位。这就是为什么您的程序可能会在某些编译器上给出奇怪结果的原因。

好习惯:

  • 切勿将位域用于任何目的。
  • 避免将有符号int类型用于任何形式的位操作。
于 2018-12-19T14:51:52.597 回答
58

我无法理解,我们怎么可能设置了一些东西,然后它就没有按原样显示。

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

是的,理想情况下它应该给你一个错误。如果您使用编译器的警告,它确实如此。在 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 的声明:“切勿将位域用于任何目的。” 如果您有充分的理由了解低级别和具体的内存布局细节,这会让您认为首先需要位域,那么您几乎肯定会遇到其他相关要求,因为它们的规格不足。

(TL;DR - 如果您足够成熟以合法地“需要”位字段,那么它们的定义不够明确,无法为您服务。)

于 2018-12-19T14:45:13.600 回答
23

这是实现定义的行为。我假设您正在运行它的机器使用二进制恭维符号整数,并int在这种情况下将其视为有符号整数,以解释为什么您不输入 if 语句的 if true 部分。

struct mystruct { int enabled:1; };

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

本质上,在处理有符号位字段时,最大值就是2^(bits - 1) - 1这种0情况。

于 2018-12-19T14:52:05.403 回答
10

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

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

1 位有符号整数可以保存的值是-2^(n-1)= -2^(1-1)= -2^0= -12^n-1= 2^1-1=0

于 2018-12-19T14:57:49.980 回答
8

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

12.2.4

4如果值真或假存储到bool任何大小类型的位域(包括一位位域)中,则原始bool值和位域的值应比较相等。如果枚举数的值存储在相同枚举类型的位域中,并且位域中的位数大到足以容纳该枚举类型(10.2)的所有值,则原始枚举数值和位域的值应比较相等。[ 例子:

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

—结束示例]


乍一看,粗体部分似乎可以解释。但是,当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

警告:“mystruct::enabled”太小,无法容纳“enum BOOL”的所有值 struct mystruct { BOOL enabled:1; };

输出是:

被禁用 !!(使用时enum BOOL : int

如果enum BOOL : int是 simple enum BOOL,则输出如上述标准段落所指定:

已启用(使用时enum BOOL


因此,可以得出结论,也很少有其他答案,该int类型不足以将值“1”存储在单个位位字段中。

于 2018-12-20T10:40:08.700 回答
0

您对我可以看到的位域的理解没有任何问题。我看到的是您首先将 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");
}
于 2019-01-04T01:47:32.287 回答