【问题标题】:Should use of bit-fields of type int be discouraged? [closed]是否应该不鼓励使用 int 类型的位域? [关闭]
【发布时间】:2014-09-20 03:37:54
【问题描述】:

来自 C++ 标准草案 (N3337):

9.6 位域

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

该标准对其他类型的位字段的任何此类行为没有作出承诺。为了了解 g++ (4.7.3) 如何处理其他类型的位域,我使用了以下测试程序:

#include <iostream>

enum TestEnum
{
   V1 = 0,
   V2
};

struct Foo
{
   bool          d1:1;
   TestEnum      d2:1;
   int           d3:1;
   unsigned int  d4:1;
};

int main()
{
   Foo foo;
   foo.d1 = true;
   foo.d2 = V2;
   foo.d3 = 1;
   foo.d4 = 1;

   std::cout << std::boolalpha;

   std::cout << "d1: " << foo.d1 << std::endl;
   std::cout << "d2: " << foo.d2 << std::endl;
   std::cout << "d3: " << foo.d3 << std::endl;
   std::cout << "d4: " << foo.d4 << std::endl;
   std::cout << std::endl;

   std::cout << (foo.d1 == true) << std::endl;
   std::cout << (foo.d2 == V2) << std::endl;
   std::cout << (foo.d3 == 1) << std::endl;
   std::cout << (foo.d4 == 1) << std::endl;

   return 0;
}

输出:

d1:真 d2:1 d3:-1 d4:1 真的 真的 错误的 真的

我对与Foo::d3 对应的输出行感到惊讶。输出与ideone.com 相同。

由于标准对int 类型的位域的比较没有明确规定,g++ 似乎没有违反标准。这让我想到了我的问题。

使用int 类型的位域是个坏主意吗?是否应该气馁?

【问题讨论】:

  • 好吧,我至少会为位字段使用unsigned 类型,任何signed 类型在语义上都没有真正意义。

标签: c++ bit-fields


【解决方案1】:

是的,int 类型的位字段是个坏主意,因为它们的符号是实现定义的。请改用signed intunsigned int

对于非位域声明,类型名称int 完全等同于signed int(或int signed,或signed)。 shortlonglong long 遵循相同的模式:未修饰的类型名称是 signed 版本,您必须添加 unsigned 关键字来命名相应的 unsigned输入。

由于历史原因,位域是一种特殊情况。使用int 类型定义的位域或者signed int 的相同声明或unsigned int 的相同声明等效。选择是实现定义的(即取决于编译器,而不是程序员)。位域是唯一一个intsigned int 不是(必然)同义的上下文。这同样适用于charshortlonglong long

引用 C++11 标准,第 9.6 节 [class.bit]:

它是实现定义的是否普通(既不明确 签名或未签名)charshortintlonglong long 位域是 签名或未签名。

(我不完全确定这样做的理由。非常旧的 C 版本没有 unsigned 关键字,并且无符号位字段通常比有符号位字段更有用。可能是早期的C 编译器在引入unsigned 关键字之前实现了位域。默认情况下使位域无符号,即使声明为int,可能只是为了方便。除了避免之外,没有真正的理由保留该规则破坏旧代码。)

大多数位字段都是无符号的,这当然意味着它们应该这样定义。

如果您想要一个 有符号 位字段(例如,一个 4 位字段,可以表示从 -8 到 +7 的值,或在非二进制补码上表示从 -7 到 +7 的值system),那么您应该明确地将其定义为signed int。如果将其定义为int,那么一些编译器会将其视为unsigned int

如果你不关心你的位域是有符号还是无符号,那么你可以将它定义为int——但是如果你定义一个位域,那么你几乎可以肯定 关心它是签名的还是未签名的。

【讨论】:

    【解决方案2】:

    您绝对可以使用不大于unsigned int 大小的任何大小的unsigned 位域。虽然signed 位域是合法的(至少在宽度大于一的情况下),但我个人不喜欢使用它们。但是,如果您确实想要使用有符号位域,则应将其显式标记为 signed,因为它取决于实现,即不合格的 int 位域是有符号还是无符号。 (这类似于char,但没有明确不合格的char* 文字的复杂功能。)

    因此,我同意不鼓励使用 int 位域。 [注 1] 虽然我不知道 int 位域是隐式无符号的任何实现,但它肯定是标准允许的,因此如果您不明确,则有很多实现特定于实现的意外行为的机会关于标志。

    标准规定有符号整数表示由可选填充位、一个符号位和值位组成。虽然标准不保证至少有一个值位,但正如 OP 中的示例所示,gcc 并不坚持存在 - 我认为这是对标准的合理解释,因为它明确允许没有填充位,并且没有任何对应于值位的此类措辞。

    无论如何,只允许三种可能的签名表示:

    • 2 的补码,其中由1 组成的一位字段应解释为-1

    • 1 的补码和符号大小。在这两种情况下,由1 组成的单个位字段被允许作为陷阱表示,因此可以在 1 位有符号位字段中表示的唯一数字是0

    由于可移植代码不能假设 1 位有符号位域可以表示任何非零值,因此坚持有符号位域至少有 2 位似乎是合理的,无论您是否解释标准( s) 实际需要与否。


    注意事项:

    1. 确实,如果不是因为字符串字面量是明确不合格的,我宁愿总是指定unsigned char。但在这一点上没有办法回滚历史。)

    【讨论】:

      【解决方案3】:

      intsigned,在C++中可以使用Two's complement,所以在第一个int的字节符号中可以存储。当signed int有2位时,可以等于1,see it working

      【讨论】:

      • @Praetorian 不过,他们要求使用符号位。所以 1 位有符号字段没有多大意义。
      • @rici 在我发表评论时,答案暗示总是使用二进制补码。我只是指那部分。
      【解决方案4】:

      这完全合乎逻辑。 int 是有符号整数类型,如果底层架构使用二进制补码来表示有符号整数(就像所有现代架构一样),那么高位就是符号位。因此,1 位有符号整数位域可以采用值 0-1。例如,一个 3 位有符号整数位域的值可以介于 -43 之间。

      只要您了解二进制补码表示,就没有理由全面禁止有符号整数位域。

      【讨论】:

      • 查看 rici 和 Keith Thompson 的答案,问题不在于平台使用二进制补码表示还是一个补码表示。问题是int 在位域中使用时可以是signed intunsigned int。明确说明类型更有意义。
      • @RSahu:谢谢!我不知道int 位域可以有符号或无符号。
      • 在他们发布答案之前我也不知道。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-23
      • 2011-11-14
      • 2019-02-07
      相关资源
      最近更新 更多