位域和对齐

Bitfields and alignment

正在尝试将数据打包到数据包中。这个数据包应该是 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] = '[=11=]';

    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 字段之前能够采用的最大大小。

根据您的架构 and/or 编译器,您的数据将对齐到不同的大小。根据您的观察,我猜您看到的是 32 位对齐的结果。如果您查看 union 的 sizeof 并且超过 8 个字节(64 位)数据已被填充以进行对齐。

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

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

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

如果你希望你的位域被打包成 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;

正如最初所写,在一种合理的(常见)方案下,当当前位域中没有足够的 space 剩余时,您的位域将被拆分为新的 32 位字。即magicparitystypesid占用25位; 32 位 unsigned int 中没有足够的空间来容纳另外 31 位,因此 mlength 存储在下一个 unsigned int 中,并且没有足够的 space ] 留在该单元中以存储 message,以便存储在第三个 unsigned int 单元中。这将为您提供一个占用 3 * sizeof(unsigned int) 或 12 个字节的结构 — 由于 uint64_t.

上的对齐要求,联合将占用 16 个字节

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