是否应该禁止使用int类型的位字段?

时间:2014-09-19 18:55:00

标签: c++ bit-fields

从草案C ++标准(N3337):

  

9.6位字段

     

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

该标准对于其他类型的位字段的任何此类行为都是非约定的。为了理解g ++(4.7.3)如何处理其他类型的位域,我使用了以下测试程序:

bool

输出:

d1: true
d2: 1
d3: -1
d4: 1

true
true
false
true

我对与#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; } 对应的输出行感到惊讶。 ideone.com处的输出相同。

由于标准对于Foo::d3类型的位字段的比较是非约束的,因此g ++似乎没有违反标准。这让我想到了我的问题。

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

4 个答案:

答案 0 :(得分:4)

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

对于非位域声明,类型名称int完全等同于signed int(或int signedsigned)。 shortlonglong long遵循相同的模式:未加修饰的类型名称是已签名的版本,您必须添加{{1 }}关键字命名相应的无符号类型。

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

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

  

它是实现定义的是否是普通的(既不是明确的   签名或未签名) long long char short {{1} } ,或 int 位字段   签名或未签名。

(我不完全确定这个的基本原理。非常旧版本的C没有long关键字,无符号位字段通常比有符号位字段更有用。可能是一些早期的C编译器在引入long long关键字之前实现了位字段。默认情况下将位字段设置为无符号,即使声明为unsigned,也可能只是为了方便。没有真正的理由保留规则,以避免破坏旧代码。)

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

如果你想要一个签名的位字段(比如说,一个4位字段可以表示-8到+7之间的值,或者非二进制补码中的-7到+7之间的值)系统),然后你应该明确地将其定义为unsigned。如果您将其定义为int,则某些编译器会将其视为signed int

如果您不关心您的位字段是有符号还是无符号,那么您可以将其定义为int - 但如果您要定义位字段,那么您几乎可以肯定做< / em>关心它是签名还是未签名。

答案 1 :(得分:2)

您绝对可以使用任何大小不超过unsigned大小的unsigned int位字段。虽然signed位字段是合法的(至少如果宽度大于1),我个人不希望使用它们。但是,如果您确实想要使用带符号的位字段,则应将其明确标记为signed,因为它与实现有关,是指非int位字段是有符号还是无符号。 (这类似于char,但没有明确不合格的char*文字的复杂特征。)

所以在这个程度上,我同意不鼓励int位字段。 [注1]虽然我不知道任何int位域是隐式无符号的实现,但标准肯定允许这样做,因此如果实现特定的意外行为有很多机会,你没有明确的迹象。

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

在任何情况下,只允许三种可能的签名表示:

  • 2&#39的补码,其中由1组成的单比特字段应解释为-1

  • 1&#39的补码和符号幅度。在这两种情况下,允许由1组成的单比特字段作为陷阱表示,因此可以在1比特有符号比特字段中表示的唯一数字是0

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


注意:

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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

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