C 中位域和联合的大小

Sizes of bit fields and unions in C

我有以下代码:

#pragma pack(push, 1)
typedef struct __attribute__((packed)){
    uint64_t msg: 48;
    uint16_t crc: 12;
    int : 0;
} data_s;
#pragma pack(pop)

typedef union {
    uint64_t tot;
    data_s split;
} data_t;

int main() {
    data_t data;
    printf(
        "Sizes are: union:%d,struct:%d,uint64_t:%d\n",
        sizeof(data),
        sizeof(data.split),
        sizeof(data.tot)
    );
    return 0;
}

我得到的输出是Sizes are: union:16,struct:10,uint64_t:8

这里我有两个问题,

  1. 即使我正在使用位字段并尝试打包它,但我得到了 10 个字节,即使位数少于 64(48+12=60) 并且可以打包分成 8 个字节。

  2. 虽然联合的两个成员的最大大小是10,为什么它的大小是16?

另外,如何将这些位打包成 8 个字节?

这是实现定义;位的布局方式取决于您的编译器。

如果位域类型不同,许多编译器会拆分位域。您可以尝试将 crc 的类型更改为 uint64_t 以查看是否有所不同。

如果您想编写可移植代码并且布局很重要,那么根本不要使用位域。

关于你的问题 2. : 一个工会总是 space 与其最大的成员一样多。在这里,它认为 struct split 的大小为 10,然后在编译以对齐内存(推荐)时可能有优化标志,使其成为 2 的幂(从 10 到 16)。

您正在分配一个整数类型,然后告诉您要使用多少位。

然后你分配另一个整数类型并告诉使用多少位。

编译器将它们放在各自的积分中。要将它们放在一个完整的字段中,请使用逗号分隔它们,例如:

uint64_t msg: 48, crc: 12;

(但请注意 user694733 提到的实现定义方面)

  1. Even though I'm using bit fields and trying to pack it, I am getting 10 bytes even though the number of bits is less than 64(48+12=60) and can be packed into 8 bytes.

首先请注意,与大多数 #pragma 一样,#pragma pack 是具有实现定义行为的扩展。 C 语言确实定义了它的行为。

其次,C 在如何布置结构内容方面为实现提供了相当大的自由,尤其是在位域方面。事实上,假设 uint64_t 是您实现中与 unsigned int 不同的类型,您是否可以首先拥有前一种类型的位域是实现定义的。

C 不会让它完全打开,但是。这是结构中位域布局规范的关键部分:

An implementation may allocate any addressable storage unit large enough to hold a bit- field. 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.

(C2011, 6.7.2.1/11;已强调)

请注意,C 并没有 表示位域成员的声明类型与存储其位的可寻址存储单元的大小有任何关系(两者都不是那里也没有其他地方),尽管实际上一些编译器确实实现了这种行为。另一方面,它所说的确实让我期望如果 C 首先接受一个 48 位位域,那么紧随其后的 12 位位域应该存储在同一个单元中。实现定义的打包规范甚至都没有出现。因此,您的实施在这方面似乎不符合要求。

  1. Even though the maximum size of the two members of the union is 10, why is its size 16?

联合可以有尾部填充,就像结构一样。填充将被引入到联合的布局中,以支持编译器对该类型的对象及其成员进行最佳对齐的想法。特别是,您的结构可能至少有 8 字节对齐要求,因此联合被填充到一个大小,该大小是该对齐要求的倍数。这又是实现定义的,只要我们在那里,您就有可能通过指示编译器也打包联合来避免填充。

Also how do I pack the bits into 8 bytes?

您可能做不到,但您应该查看编译器的文档。由于观察到的行为似乎不合规,因此任何人都可以猜测您可以或必须做什么。

不过,如果是我,我会尝试的第一件事就是删除 pragma 和 attribute;删除零长度位域,并更改 crc 位域的声明类型以匹配前面的位域 (uint64_t)。所有这些的目的是清除可能会混淆编译器的细节,以便首先尝试让它呈现标准要求的行为。如果您可以使 struct 以 8 个字节的形式出现,那么您可能不需要做任何更多的事情来使联合出现相同的大小(因为 8 是一个有点神奇的数字)。

这些是位域。它们很少被标准化覆盖。如果你使用它们 - 你就靠你自己了。

  • #pragma pack(push, 1) typedef struct __attribute__((packed)){
    这些是 gcc 编译器的非标准编译器扩展。任何标准都没有涵盖添加它们时会发生什么。该标准唯一说的是,如果编译器无法识别 #pragma,它必须忽略该行。
  • C标准只保证类型_Boolunsigned intsigned int对位域有效。您使用 uint64_tuint16_t。 C 标准未涵盖您执行此操作时发生的情况 - 这是实现定义的行为。标准提到 "units",但没有指定 "unit" 有多大。
  • msg: 48; C 标准没有指定这是最低有效的 48 位还是最高有效位。它不指定分配顺序,不指定对齐方式。在此基础上添加字节顺序,您无法真正了解这段代码的作用。
    所有 C 标准保证 msg 位于比尾部结构成员更低的地址。除非它们被合并到同一个位域中——否则该标准没有任何保证。完全由实现定义。
  • int : 0; 在位域末尾添加是没有用的,这段代码的唯一目的是让编译器不要将任何尾随位域合并到前一个位域中。
  • 据我所知,
  • #pragma pack 和类似的东西不能保证 struct/union.
  • 末尾没有尾随填充
  • 众所周知,gcc 与位域一起使用时会表现得很奇怪。它与曾经编写的每个 C 编译器都有这个共同点。

因此,您的问题的答案可以概括为:因为位域。


另一种 100% 确定性、定义明确的可移植且安全的方法是这样的:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

typedef uint64_t data_t;

static inline uint64_t msg (data_t* data)
{
  return *data >> 12;
}

static inline uint64_t crc (data_t* data)
{
  return *data & 0xFFFu;
}

int main() {
    data_t data = 0xFFFFFAAAu;

    printf("msg: %"PRIX64" crc:%"PRIX64, msg(&data), crc(&data));
    return 0;
}

这甚至可以跨 CPU:s 不同的字节顺序移植。