【问题标题】:C - Why #pragma pack(1) Consider 6-bit struct member as an 8-bit?C - 为什么#pragma pack(1) 将 6 位结构成员视为 8 位?
【发布时间】:2018-08-21 06:58:34
【问题描述】:

我在定义 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)

【问题讨论】:

  • 在位域方面没有“正确的行为”,因为标准对它们的规定太差了。并且以 1 字节对齐方式打包意味着 8 位,是吗?没有#pragma pack(0.6)。此外,在使用 VS 时询问符合行为是无稽之谈。
  • 我想编译器不想打扰所有这些位移和东西。如果你想打包使用 Ada.*
  • 3 字节的布局需要将 cChar2(和 cChar3)放在 2 字节上,与 @987654342 相比,它们需要更复杂的操作(2 字节访问 + 移位和屏蔽)来访问@(或cChar4)(1 字节访问 + 更简单的移位和屏蔽)。我怀疑 MS 根本不想实现更多以前的类型,而是与下一个字节边界对齐。
  • @user694733 我在structs 之前在Qt 中这样定义,没有任何问题。我预计它会在visual studio 中表现出相同的行为
  • @IInspectable 对他们有好处!这个问题被标记为 C,据我所知,VS2017 还不是 28 年前发布的 ISO 9899:1990 的合规实现。值得注意的是,从那时起,C 标准已经更改了 3 次(如果将 C95 算作主要更新,则更改了 4 次)。

标签: c winapi struct pragma-pack


【解决方案1】:

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

n(可选):指定用于打包的值,以 字节 为单位。

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

一个实现可以分配任何大到足以容纳位域的可寻址存储单元。如果有足够的空间剩余,紧跟在结构中另一个位域之后的位域将被打包到同一单元的相邻位中。 如果剩余空间不足,是否将不适合的位域放入下一个单元或与相邻单元重叠是实现定义的。单元内的字段(高阶到低阶或低阶到高阶)是实现定义的。未指定可寻址存储单元的对齐方式。

More of the MS documentation 指出了这种特定行为:

如果整数类型的大小相同并且如果下一个位字段适合当前分配单元而不跨越由位域的常见对齐要求。

【讨论】:

  • 顺便说一句,我在Qt 中测试了这样的structs 并且它有效而且我预计在VS 中的这项工作就像在qt 中一样
【解决方案2】:

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

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

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

【讨论】:

  • 是的,我使用 6 位字段将 3 个字节复制到这个结构中,通过访问每个成员,我可以得到一个6-bit 数字。
【解决方案3】:

在某些实现中,位字段不能跨越变量边界。只有当它们的总位数适合该变量的数据类型时,您才能在变量中定义多个位字段。

在您的第一个示例中,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

【讨论】:

  • 其实标准上说能不能跨越边界,是由实现决定的。你可能想澄清一下。
  • @paxdiablo 也许是这样,但这显然是 OP 编译器实现的行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-19
  • 1970-01-01
  • 2013-07-24
  • 2013-09-03
  • 2017-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多