【问题标题】:Casting uint64_t on bitfield在位域上转换 uint64_t
【发布时间】:2019-12-13 11:38:11
【问题描述】:

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

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;

    ...
}

【问题讨论】:

  • 问题在于,即使没有严格的别名,位域也会产生高度不可移植的代码。字节序、打包、字节顺序以及这些字段的所有内容都取决于特定的编译器实现。但是,是的,它也可能违反了严格的别名。能否提供更多细节,例如硬件平台和编译器?
  • 我的编译器是 Ubuntu 18.04 上的 VSCode (GCC)。编译的程序运行良好,但我的问题是我不理解这个特定转换背后的语法。赋予数据什么价值?是指针吗?它指出了什么?
  • @th33lf 它可能也违反了严格的别名没有“可能” - 发布的代码违反了strict aliasing

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


【解决方案1】:

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

是的,它会,所以代码会调用未定义的行为。它也非常不便携:

  • 我们不知道给定系统使用的称为“可寻址存储单元”的抽象项目的大小。它不一定是 64 位,所以理论上可能有填充和其他隐藏在位域中的讨厌的东西。 64 位 unsigned 有问题。

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

如果需要访问uint64_t 的各个位(字段),我建议使用按位移位来进行,因为这使得代码即使在不同的字节序架构之间也完全可移植。那么你也不需要不可移植的ntohll 调用。

【讨论】:

    【解决方案2】:

    它做什么(或试图做什么)非常简单。

    uint64_t tmp = ntohll(*raw);
    

    这一行获取指针 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 也是合法的,因为它将数据视为字符数组。

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

    【讨论】:

      【解决方案3】:

      从右到左:

      &tmp取tmp地址

      (bitfield_struct *)&tmp tmp 的地址是位域结构类型数据的地址

      *(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

      【讨论】:

      • @Rafael:不幸的是,它没有帮助——它是错误的和误导性的。虽然代码似乎uint64_t 视为bitfield_struct 并从中获取位字段,但基于对强制类型转换和指针取消引用的幼稚理解,该行为实际上并未由C 标准,因此不可靠。如果它是可移植的,您不应该使用这样的代码。相反,可以使用memcpyuint64_t 的内容复制到bitfield_struct 中。这提供了一种将uint64_t 的字节重新解释为bitfield_struct 的可移植方式。
      • 正确重新解释的示例是uint64_t tmp = ntohll(*raw); bitfield_struct data; memcpy(&data, &tmp, sizeof data);(假设ntohlluint64_tbitfield_struct 中涉及的大小匹配)。事实上,虽然一些 C 实现可能会通过定义这样的别名来扩展 C 标准以工作,但简单可移植方法的可用性意味着应该使用可移植方法,并且依赖编译器扩展是没有意义的。
      • @EricPostpischil 是的,我了解此解决方案的含义。位域根本不是便携式解决方案的最佳方法,所以您能否建议另一种具有相似属性的方法,或者我在哪里可以学习这些方法?我找到了其他声明特定位大小const uint8_t BIT_BYTE = 0x1; const uint8_t BIT_HW = 0x2; 等的方法,但我认为它不会改变您提到的任何内容。
      • @Rafael:我对答案的批评不是关于位域缺乏可移植性。那是一个单独的问题。这是因为 C 标准不支持通过转换指针的别名来重新解释类型。
      • @Rafael:要以可移植的方式使用位,请在字节上使用按位和移位运算符(在 unsigned char 中,或者,一旦根据需要排序,在 uint64_t 或其他合适的无符号中整数类型)。
      猜你喜欢
      • 2018-07-22
      • 1970-01-01
      • 2017-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多