在位域上投射uint64_t

时间:2019-12-13 11:38:11

标签: c casting endianness bit-fields strict-aliasing

我找到了将位域用于网络消息的代码。我想知道强制转换bitfield_struct data = *(bitfield_struct *)&tmp;的作用以及它的语法如何工作。它不会违反严格的别名规则吗?这是代码的一部分:

typedef struct  
{
    unsigned      var1    : 1;
    unsigned      var2    : 13;
    unsigned      var3    : 8;
    unsigned      var4    : 10;
    unsigned      var5    : 7;
    unsigned      var6    : 12;
    unsigned      var7    : 7;
    unsigned      var8    : 6;

} bitfield_struct;

void print_data(u_int64_t * raw, FILE * f, int no_object)
{
    uint64_t tmp = ntohll(*raw);

    bitfield_struct data = *(bitfield_struct *)&tmp;

    ...
}

3 个答案:

答案 0 :(得分:1)

  

它不会违反严格的别名规则吗?

是的,因此代码将调用未定义的行为。它也是高度不可移植的:

  • 我们不知道给定系统使用的称为“可寻址存储单元”的抽象项目的大小。它不一定是64位,因此理论上在位字段中可能会隐藏填充和其他令人讨厌的东西。 64位unsigned可疑。

  • 我们也不知道位域是否使用与uint64_t相同的位序。我们也不知道他们是否使用相同的字节序。

如果需要访问uint64_t的各个位(字段),我建议使用按位移位,因为这样即使在不同的字节序体系结构之间也可以完全移植代码。然后,您也不需要非便携式ntohll调用。

答案 1 :(得分:0)

它所做的(或尝试做的)非常简单。

uint64_t tmp = ntohll(*raw);

此行采用原始指针中的值,反转字节顺序并将其复制到temp。

bitfield_struct data = *(bitfield_struct *)&tmp;

此行将temp(原为uint64)中的数据重新解释为类型bitfield_struct并将其复制到数据中。这基本上是等效的:

/* Create a bitfield_struct pointer that points to tmp */
bitfield_struct *p = (bitfield_struct *)&tmp;

/* Copy the value in tmp to data */
bitfield_struct data = *p;

这是因为通常bitfield_structuint64是不兼容的类型,您不能仅用bitfield_struct data = tmp;来分配一个

该代码大概会继续通过data访问位域中的字段,例如data.var1

现在,就像人们指出的那样,有几个问题使此代码不可靠且不可移植。

  1. 位字段在很大程度上取决于实现。解?阅读手册,了解您的特定编译器变体如何处理位字段。或者根本不使用位域。

  2. 不能保证uint64_t和bitfield_struct具有相同的对齐方式。这意味着可能存在填充,这些填充可以完全抵消您的期望并使您最终得到错误的数据。一种解决方案是使用memcpy而不是指针进行复制,这可能会让您遇到此特定问题。或使用编译器提供的机制指定打包对齐方式。

  3. 当应用严格的别名规则时,该代码将调用UB。解?大多数编译器都会有一个no-strict-aliasing标志可以启用,但会降低性能。甚至更好的是,使用bitfield_structuint64_t创建并集类型,并使用它在彼此之间重新解释。即使使用严格的混叠规则,也允许这样做。使用memcpy也是合法的,因为它将数据视为一个字符数组。

但是,最好的办法是根本不使用这段代码。您可能已经注意到,它过于依赖编译器和平台特定的东西。相反,请尝试使用位掩码和移位来完成相同的操作。这摆脱了上面提到的所有三个问题,不需要特殊的编译器标志或必须面对任何实际的可移植性问题。最重要的是,它免除了其他开发人员阅读您的代码的麻烦,而不必担心将来发生此类事情。

答案 2 :(得分:-2)

从右到左:

&tmp以tmp地址

(bitfield_struct *)&tmp tmp的地址是指向bitfield_struct类型数据的地址

*(bitfield_struct *)&tmp从tmp中提取值,假设它是bitfield_struct数据

bitfield_struct data = *(bitfield_struct *)&tmp;将tmp存储到数据,假设tmp是bitfield_struct

因此,它只是使用额外的指针进行复制,以避免编译错误/不兼容类型的警告。

您可能不了解的是结构的位寻址。

unsigned var1 : 1; unsigned var2 : 13;

在这里您将找到有关它的更多信息:https://www.tutorialspoint.com/cprogramming/c_bit_fields.htm