位域和对齐

3

尝试将数据打包到一个数据包中。该数据包应为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`字段。 这个页面证实了这一点:保存位域的分配单元的对齐方式未指定 但如果是这种情况,人们如何在位域中打包数据呢,这毕竟是它们存在的目的?
在`.message`字段被清空之前,24位似乎是`.mlength`字段能够承载的最大大小。

3
通常情况下,您不能依赖于位域中数据的布局。如果您需要将多个数据打包进一个字中,您必须手动完成编组操作。 - fuz
1
不要依赖于特定的内存布局来处理C数据结构。因为有太多的实现定义和编译器特定的参数。正如@FUZxxl所写,使用适当的编组技术。在好的编译器下,这并不一定会更慢。 - too honest for this site
1
@Olaf 如果你知道自己在做什么,使用标准类型并不是一个坏主意。 - fuz
@FUZxxl:使用固定宽度类型的方法已经是一个不错的想法了。我们为什么要改用具有实现定义范围和符号的类型呢? - too honest for this site
@Olaf 因为固定宽度类型可能并非在所有地方都可用,也可能不是必需的。 - fuz
显示剩余3条评论
2个回答

4
几乎关于位域的布局的所有东西都是标准中实现定义的,正如你从SO上众多其他问题中所发现的一样。(其中之一是,你可以查看有关位域的问题,特别是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位字。也就是说,magic、parity、stype和sid将占用25位;在32位的unsigned int中没有足够的空间来存储另外31位,因此mlength存储在下一个unsigned int中,并且在该单元中没有剩余空间来存储message,因此将其存储在第三个unsigned int单元中。这将给您提供一个占用3 * sizeof(unsigned int)或12个字节的结构 - 由于uint64_t的对齐要求,联合将占用16个字节。

请注意,标准不能保证我展示的内容能够工作。但是,在许多编译器下,它可能会起作用。(具体而言,在Mac OS X 10.11.4上使用GCC 5.3.0可以正常运行。)


1
但即使如此,您也不能依赖于位域的布局顺序。更换编译器后,一切都可能会改变。而且要精确地说,当一个位域无法适应当前结构元素时,它是否必须分裂成一个新的结构元素也是实现定义的 - 它可能跨越元素。 - Andrew Henle
就像我说的那样,除了它们的存在之外,基本上每件事情都是实现定义的。在C中,关于位域的内存管理问题,Bit field's memory management in C 这个问题有一个答案(来自YT),广泛引用了标准。 - Jonathan Leffler

0

根据您的架构和/或编译器,您的数据将对不同的大小进行对齐。从您的观察中,我猜测您正在看到32位对齐的后果。如果您查看您的联合sizeof大于8字节(64位),则已填充数据以进行对齐。

通过32位对齐,如果mlength和message的总和小于或等于32位,它们只能相邻。这可能是您在24位限制下看到的情况。

如果您希望您的结构体在32位对齐时仅占用64位,则需要稍微重新排列它。单个位奇偶校验应与31位mlength相邻,并且您的4个8位变量应该分组在一起。


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