C中的位字段和联合的大小

时间:2018-01-25 13:59:31

标签: c gcc struct unions bit-fields

我有以下代码:

myArray = np.zeros(20, dtype=np.int8)
np.savetxt( 'np_tmp.txt', myArray, fmt='%d' )

for i in range(10):
    np.savetxt( 'np_tmp.txt',  np.append( np.delete ( np.loadtxt( 'np_tmp.txt', dtype=np.int8 ),0 ), i ), fmt='%d' )

我得到的输出是#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; }

这里我有两个问题,

  1. 即使我使用位字段并尝试打包它,即使位数小于64(48 + 12 = 60),我也会获得10个字节,并且可以打包成8个字节字节。

  2. 即使两个成员的最大尺寸是10,为什么它的尺寸为16?

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

5 个答案:

答案 0 :(得分:1)

这是实现定义;如何布置位取决于您的编译器。

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

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

答案 1 :(得分:1)

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

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

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

uint64_t msg: 48, crc: 12;

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

答案 2 :(得分:1)

这些是位字段。它们很难被标准化所覆盖。如果你使用它们 - 你是独立的。

  • #pragma pack(push, 1) typedef struct __attribute__((packed)){
    这些是gcc编译器的非标准编译器扩展。添加它们时会发生什么不受任何标准的约束。标准所说的唯一内容是,如果编译器没有识别#pragma,它必须忽略该行。
  • C标准仅保证_Boolunsigned intsigned int类型对位字段有效。您使用uint64_tuint16_t。当你做的事情没有被C标准所涵盖时 - 这是实现定义的行为。该标准涉及"单位",但没有说明一个"单位"是。
  • msg: 48; C标准未指定这是最低有效48位还是最重要位。它没有指定分配顺序,也没有指定对齐方式。在此基础上添加字节顺序,您无法真正了解此代码的作用 所有C标准保证是msg驻留在比结尾成员更低的地址上。除非将它们合并到相同的位字段中,否则标准不会保证。完全实现定义。
  • int : 0;在位字段末尾添加是没用的,此代码的唯一目的是让编译器不将任何尾随位字段合并到前一个字段中。
  • #pragma pack和类似的做法,据我所知,保证结构/联合的末尾没有尾随填充。
  • 众所周知,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上移植:具有不同的字节顺序。

答案 3 :(得分:0)

对于你的问题2.:联盟总是占用与其最大成员一样多的空间。这里它认为struct split的大小为10,然后在编译为对齐内存(建议使用)时可能有优化标志,使其为2的幂(从10到16)。

答案 4 :(得分:0)

  
      
  1. 即使我使用位字段并尝试打包它,即使位数小于0,我也会得到10个字节   64(48 + 12 = 60),可以打包成8个字节。
  2.   

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

第二,C在实现结构内容的方式方面提供了相当大的自由度,特别是在位域方面。实际上,假设uint64_t与您实现中的unsigned int不同,那么您是否可以首先获得前一类型的位域是实现定义的。

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

  

实现可以分配任何可寻址的存储单元   足以容纳一个位域。 如果剩余足够的空间,那就是一个位字段   紧接着结构中的另一个位字段应该被打包   进入相同单位的相邻位。 如果空间不足,   是否将不适合的位域放入下一个单元或   重叠相邻单元是实现定义的。的顺序   单位内的位域分配(高位到低位或   低阶到高阶)是实现定义的。对齐   可寻址存储单元未指定。

C2011, 6.7.2.1/11;重点补充)

请注意,C 表示声明的位域成员类型与存储其位的可寻址存储单元的大小有关(既不存在也不存在其他地方)虽然实际上有些编译器确实实现了这种行为。另一方面,它所说的肯定会让我期望如果C首先接受48位位域,那么紧随其后的12位位域应该存储在同一个单元中。实施定义的包装规格甚至不进入图片。因此,在这方面,您的实施似乎是不符合的。

  
      
  1. 即使联盟的两个成员的最大尺寸是10,为什么它的尺寸为16?
  2.   

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

  

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

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

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