C - 为什么#pragma pack(1) 将 6 位结构成员视为 8 位?

C - Why #pragma pack(1) Consider 6-bit struct member as an 8-bit?

我在定义 6-bit 字段并将其假定为 8-bit 时遇到了 #pragma pack(1) 错误行为。我阅读 this question 来解决我的问题,但它对我一点帮助都没有。

Visual Studio 2012 中,我在下面定义了 struct 以保存 Base64 个字符:

#pragma pack(1)
struct BASE64 {
    CHAR    cChar1 : 6;
    CHAR    cChar2 : 6;
    CHAR    cChar3 : 6;
    CHAR    cChar4 : 6;
};

现在我用 sizeof 得到了它的大小,但结果不是我预期的:

printf("%d", sizeof(BASE64));      // should print 3

结果:4

我期望得到 3(因为 6 * 4 = 24,所以 24 位是 3 字节)

事件我用 1-bit 字段测试它并得到正确的大小(1 字节):

#pragma pack(1)
struct BASE64 {
    CHAR    cChar1 : 2;
    CHAR    cChar2 : 2;
    CHAR    cChar3 : 2;
    CHAR    cChar4 : 2;
};

实际上,为什么 6-bit 假设 8-bit#pragma pack(1)

#pragma pack 通常在字节边界而不是位边界上打包。这是为了防止在要压缩的字段之间插入填充 bytes。来自 Microsoft's documentation(因为您提供了 winapi 标签,并且我强调):

n (optional) : Specifies the value, in bytes, to be used for packing.

当您试图让位域跨越字节边界时,实现如何处理位域是实现定义的。来自 C11 标准(第 6.7.2.1 Structure and union specifiers /11 节,再次强调):

An implementation may allocate any addressable storage unit large enough to hold a bitfield. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

More of the MS documentation 调用此特定行为:

Adjacent bit fields are packed into the same 1-, 2-, or 4-byte allocation unit if the integral types are the same size and if the next bit field fits into the current allocation unit without crossing the boundary imposed by the common alignment requirements of the bit fields.

在某些实现中,位域不能跨越变量边界。您可以在一个变量中定义多个位字段,前提是它们的总位数符合该变量的数据类型。

在您的第一个示例中,CHAR 中没有足够的可用位来容纳 cChar1cChar2,当它们各自为 6 位时,因此 cChar2必须进入内存中的下一个CHAR。与 cChar3cChar4 相同。因此,为什么 BASE64 的总大小是 4 个字节,而不是 3 个字节:

  (6 bits + 2 bits padding) = 8 bits
+ (6 bits + 2 bits padding) = 8 bits
+ (6 bits + 2 bits padding) = 8 bits
+ 6 bits
- - - - - - - - - - 
= 30 bits
= needs 4 bytes

在您的第二个示例中,CHAR 中有足够的可用位来容纳所有 cChar1...cChar4,当它们每个都是 1 位时。因此,为什么 BASE64 的总大小是 1 个字节,而不是 4 个字节:

  1 bit
+ 1 bit
+ 1 bit
+ 1 bit
- - - - - - - - - - 
= 4 bits
= needs 1 byte

简单的回答是:这不是错误的行为

打包试图以字节为单位放置单独的数据块,但它不能将两个 6 位块打包到一个 8 位字节中。所以编译器将它们放在不同的字节中,可能是因为访问单个字节来检索或存储 6 位数据比访问两个连续的字节并处理一个字节的一些尾部部分和另一个字节的一些前导部分更容易。

这是实现定义的,您对此无能为力。优化器可能有一个更喜欢大小而不是速度的选项——也许您可以使用它来实现您的预​​期,但我怀疑优化器会那么远。无论如何,大小优化通常会缩短代码,而不是数据(据我所知,但我不是专家,我在这里很可能是错的)。