理解C++中的位域打包

4

我正在尝试理解位域(bitfields)。下面的示例来自C++在线文档

#include <iostream>
struct S {
// will usually occupy 2 bytes:
// 3 bits: value of b1
// 2 bits: unused
// 6 bits: value of b2
// 2 bits: value of b3
// 3 bits: unused
unsigned char b1 : 3, : 2, b2 : 6, b3 : 2;
};
int main()
{
    std::cout << sizeof(S) << '\n'; // usually prints 2
}

这个例子让我困惑的是,在代码上面的注释中,它说在b1:3之后有2个未使用的比特。然后在b3:2之后有3个未使用的比特。为什么?不应该是unsigned char类型中定义的比特数吗?或者是到下一个分配单元边界还剩余的未使用比特数?
3个回答

1
将所有字段声明都放在一行上,会让人有些难以理解。下面是同样的内容,但重新格式化了一下:
struct S {
  unsigned char b1:3;  // 3 bits - b1
  unsigned char   :2;  // 2 bits - unused
  unsigned char b2:6;  // 6 bits - b2
  unsigned char b3:2;  // 2 bits - b3
                       // Total: 13 bits
};                     // 3 bits - unused (implicit padding)

两个“未使用”的部分是:(1)在b1之后具有显式宽度为2位的未命名字段;以及(2)填充结构体末尾,使其舍入到16位(下一个unsigned char单元)。

那么未命名字段是字节或单词内的一种填充吗? - Michael IV
是的。在这种情况下,未命名字段的行为与任何命名字段相同(宽度为0的未命名字段具有特殊行为——这是您链接页面上的下一个示例)。 - 一二三

1
我不同意你正在阅读的文档。引用C++标准中的话:“类对象内位域的分配是实现定义的”。
一些编译器会扩展位域。如果您这样做。
unsigned x : 3 ;

编译器在分配内存方面几乎可以做任何想做的事情。我用过一些编译器,它们采取了一些特殊的方法来进行内存分配。
unsigned x : 1 ;

把它转换成32位整数(性能最佳)。
你正在处理一些明显错误的材料。如果你想要使用实际的位,你可以选择:
1)需要确切了解编译器如何布局; 2)使用位掩码和<<、&、|、>>运算符从已知大小的整数中提取和插入值。

该文章多次指出,位域的实际分配细节是由具体实现定义的。 - 一二三
1
是的,那么问题来了?如果你依赖于位域给出一些特定的分配,那么你就会遇到麻烦。遵循“通常”的限定词会让你受到伤害,特别是当你使用MSVC时。 - user3344003
使用static_assert来验证位域分配的假设并防止任何意外情况的编译是非常容易的。 - 一二三

0

你正在处理未命名变量。这样更容易理解:

#include <iostream>
struct S {
    // will usually occupy 2 bytes:
    // 3 bits: value of b1
    // 2 bits: unused
    // 6 bits: value of b2
    // 2 bits: value of b3
    // 3 bits: unused
    unsigned char b1 : 3;
    unsigned char : 2; // How do you reference these?
    unsigned char b2 : 6, b3 : 2;
};
int main()
{
    std::cout << sizeof(S) << '\n'; // usually prints 2
}

http://ideone.com/6eCUB0

无名的变量没有名称,因此无法以正常方式引用它们。在您的情况下,它们最终只会占用一些空间,并可能用于填充原因、对齐原因或其他适用于此示例的任何其他原因。

如果将所有这些位相加,您将得到16,这在大多数系统上恰好是2个字节。顺便说一句,正如您可以读取评论中的“通常”一词是带有原因放置的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接