【问题标题】:Bitfields and alignment位域和对齐
【发布时间】:2016-04-16 19:06:22
【问题描述】:

试图将数据打包成一个数据包。此数据包应为 64 位。我有这个:

typedef union {
  uint64_t raw;
  struct {
    unsigned int magic    : 8;
    unsigned int parity   : 1;
    unsigned int stype    : 8;
    unsigned int sid      : 8;
    unsigned int mlength  : 31;
    unsigned int message  : 8;
  } spacket;
} packet_t;

但似乎不能保证对齐。因为当我运行这个时:

#include <strings.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

const char *number_to_binary(uint64_t x)
{
    static char b[65];
    b[64] = '\0';

    uint64_t z;
    int w = 0;
    for (z = 1; w < 64; z <<= 1, ++w)
    {
        b[w] = ((x & z) == z) ? '1' : '0';
    }

    return b;
}

int main(void)
{
  packet_t ipacket;
  bzero(&ipacket, sizeof(packet_t));
  ipacket.spacket.magic = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.parity = 1;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.stype = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.sid = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.mlength = 2147483647;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.message = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
}

我得到(大端):

1111111100000000000000000000000000000000000000000000000000000000
1111111110000000000000000000000000000000000000000000000000000000
1111111111111111100000000000000000000000000000000000000000000000
1111111111111111111111111000000000000000000000000000000000000000
1111111111111111111111111000000011111111111111111111111111111110
1111111111111111111111111000000011111111111111111111111111111110

我的 .mlength 字段在右侧某处丢失,但它应该就在 .sid 字段旁边。

This page 确认:包含位字段的分配单元的对齐方式未指定但如果是这种情况,人们如何将数据打包到位字段的目的是什么?

24 位似乎是.mlength 字段在.message 字段被踢出之前能够采用的最大大小。

【问题讨论】:

  • 通常,您不能依赖位域中的数据是如何布局的。如果您需要将多个数据打包成一个单词,则必须手动进行编组。
  • 你的结构是 32 * 6 位...你应该使用字符来代替。
  • 不要依赖于 C 数据结构的特定内存布局。定义的实现和特定于编译器的参数太多。正如@FUZxxl 所写,使用适当的编组。使用好的编译器,这不一定会变慢。
  • @xvan:使用标准类型是个坏主意。而char 更糟糕。
  • @Olaf 如果您知道自己在做什么,那么使用标准类型不是一个坏主意。

标签: c bit-fields


【解决方案1】:

几乎所有关于位域布局的内容都是在标准中实现定义的,正如您从关于 SO 主题的许多其他问题中发现的那样。 (除其他外,您可以查看Questions about bitfields,尤其是Bit field's memory management in C)。

如果您希望将位字段打包成 64 位,您必须相信您的编译器允许您对字段使用 64 位类型,然后使用:

typedef union {
  uint64_t raw;
  struct {
    uint64_t magic    : 8;
    uint64_t parity   : 1;
    uint64_t stype    : 8;
    uint64_t sid      : 8;
    uint64_t mlength  : 31;
    uint64_t message  : 8;
  } spacket;
} packet_t;

正如最初所写的那样,在一种似是而非的(通用)方案下,当当前位域中没有足够的空间时,您的位域将被拆分为新的 32 位字。即magicparitystypesid将占用25位; 32 位 unsigned int 中没有足够的空间来容纳另外 31 位,因此 mlength 存储在下一个 unsigned int 中,并且该单元中没有足够的空间来存储 @987654332 @ 所以存储在第三个unsigned int 单元中。这将为您提供一个占用 3 * sizeof(unsigned int) 或 12 个字节的结构 - 由于 uint64_t 的对齐要求,联合将占用 16 个字节。

请注意,标准并不能保证我展示的内容会起作用。但是,在许多编译器下,它可能会起作用。 (具体来说,它适用于 Mac OS X 10.11.4 上的 GCC 5.3.0。)

【讨论】:

  • 但即便如此,您也不能依赖位域的布局顺序。改变你的编译器,一切都会改变。准确地说,当一个位域不适合当前的结构元素时,它是否必须拆分为一个新的结构元素也是由实现定义的——它可能跨越元素。
  • 就像我说的,基本上除了它们的存在之外的所有东西都是实现定义的。 Bit field's memory management in C 问题的答案(来自 YT)广泛引用了关于位字段主题的标准。
【解决方案2】:

根据您的架构和/或编译器,您的数据将与不同的大小对齐。根据您的观察,我猜您正在看到 32 位对齐的后果。如果你看一下你的联合的大小,那是超过 8 个字节(64 位)的数据已被填充以进行对齐。

使用 32 位对齐时,mlength 和消息只有在总和小于或等于 32 位时才能彼此相邻。这可能就是您看到的 24 位限制。

如果你希望你的结构只占用 64 位和 32 位对齐,你将不得不重新排列它。单个位奇偶校验应该在 31 位 mlength 旁边,并且您的 4 个 8 位变量应该组合在一起。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-02
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-09
    • 1970-01-01
    • 2020-02-16
    相关资源
    最近更新 更多