【问题标题】:24-bit to 32-bit conversion in C++C++ 中 24 位到 32 位的转换
【发布时间】:2020-04-08 07:14:24
【问题描述】:

我需要在 C++ 中将 24 位整数(2s 补码)转换为 32 位整数。我找到了一个解决方案here,它给出了

int interpret24bitAsInt32(unsigned char* byteArray)
 {     
    return (  
        (byteArray[0] << 24)
    |   (byteArray[1] << 16)
    |   (byteArray[2] << 8)
    ) >> 8;  
}

虽然我发现它正在工作,但我对这段代码有以下担忧。 byteArray[0] 只有 8 位,因此像 byteArray[0] &lt;&lt; 24 这样的操作是如何实现的? 如果编译器将 byteArray 上转换为整数并执行操作,这将是可能的。这可能是它现在工作的原因。但我的问题是,这种行为是否在所有编译器中都得到保证并在标准中明确提及?这对我来说不是微不足道的,因为我们没有明确地向编译器提供目标是 32 位整数的任何线索!

另外,请让我知道任何像矢量化这样的即兴创作都可以提高速度(可能使用 C++11),因为我需要将大量 24 位数据转换为 32 位。

【问题讨论】:

  • 请注意,您上面提供的代码不是 C++,因为 byte[] byteArray 不是有效的 C++ 语法。这真的是关于 C++ 还是其他语言!? byte是什么类型?
  • sizeof(int) 不是必需的 4 虽然...
  • @MichaelKenzel:有std::byte (C++17)
  • @Soo 不,你把它改成了char,而不是unsigned char
  • 我不是 100% 确定,但我认为 &lt;&lt; 运算符 promotes 在它进行转变之前它的论点。这里的其他人会更加确定。当然,24 文字是 int 不是更短的。

标签: c++ c++11 binary bit-shift


【解决方案1】:
int32_t interpret24bitAsInt32(unsigned char* byteArray)
{     
    int32_t number =
        (((int32_t)byteArray[0]) << 16)
    |   (((int32_t)byteArray[1]) << 8)
    |   byteArray[2];
    if (number >= ((int32_t)1) << 23)
        //return (uint32_t)number | 0xFF000000u;
        return number - 16777216;
    return number;
}

通过将1 移入int 的符号位,此函数应该执行您想要的操作,而不会调用未定义的行为。
仅当sizeof(int) &lt; 4 时才需要int32_t 强制转换,否则默认整数提升为int

如果有人不喜欢if:它不会被编译器转换为条件跳转(gcc 9.2):https://godbolt.org/z/JDnJM2
它留下一个cmovg

【讨论】:

  • 在 if 语句中的 uint32_t 到 int32_t 转换中仍有未定义的行为。
  • @afic 我认为实现定义的行为
  • @afic 是实现定义的,但我找到了更好的解决方案,只需减去1 &lt;&lt; 24(24位数字值得符号位)。
【解决方案2】:

[expr.shift]/1 操作数应为整数或非范围枚举类型,并执行整数提升。结果的类型是提升的左操作数的类型...

[conv.prom] 7.6 整体促销

1boolchar16_tchar32_twchar_t 以外的整数类型的纯右值,其整数转换等级(7.15)小于@987654325 的等级@ 可以转换为int 类型的纯右值,如果 int 可以表示源类型的所有值;否则,源纯右值可以转换为unsigned int 类型的纯右值。

所以是的,标准要求在评估之前将类型为 unsigned char 的移位运算符的参数提升为 int


也就是说,您代码中的技术依赖于int a) 32 位大,b) 使用二进制补码表示负值。尽管这在现代系统中很常见,但标准都不能保证这两者。

【讨论】:

    【解决方案3】:

    没有分支的版本;但乘法:

    int32_t interpret24bitAsInt32(unsigned char* bytes) {
      unsigned char msb = UINT8_C(0xFF) * (bytes[0] >> UINT8_C(7));
      uint32_t number =
            (msb << UINT32_C(24))
          | (bytes[0] << UINT32_C(16)))
          | (bytes[1] << UINT32_C(8)))
          |  bytes[2];
      return number;
    }
    

    不过,您需要测试省略分支是否真的会给您带来性能优势!

    改编自我对 10 位数字执行此操作的旧代码。使用前测试!

    哦,关于将uint32_t 转换为int32_t,它仍然依赖于实现定义的行为。如果你想进入那个兔子洞,have fun but be warned

    或者,更简单:使用mchs answer 中的技巧。并且还使用移位而不是乘法:

    int32_t interpret24bitAsInt32(unsigned char* bytes) {
      int32_t const number =
            (bytes[0] << INT32_C(16))
          | (bytes[1] << INT32_C(8))
          |  bytes[2];
      int32_t const correction = 
         (bytes[0] >> UINT8_C(7)) << INT32_C(24);
      return number - correction;
    }
    

    Test case

    【讨论】:

      【解决方案4】:

      对于operator_arithmetic,确实有Integral_promotion 用于小于int 的类型

      所以假设sizeof(char) &lt; sizeof(int)

      byteArray[0] << 24
      

      byteArrayint 中提升,您在int 上进行位移。

      第一个问题是int 只能是 16 位。

      第二个问题(在 C++20 之前),int有符号,并且按位移位很容易导致实现定义或 UB(对于负 24 位数字,您两者都有)。

      在 C++20 中,按位移位的行为已被简化(行为已定义)并且有问题的 UB 也已被删除。

      负数的前导1保留在neg &gt;&gt; 8中。

      所以在 C++20 之前,你必须这样做:

      std::int32_t interpret24bitAsInt32(const unsigned char* byteArray)
      {
          const std::int32_t res =
              (std::int32_t(byteArray[0]) << 16)
            | (byteArray[1] << 8)
            | byteArray[2];
          const std::int32_t int24Max = (std::int32_t(1) << 24) - 1;
          return res <= int24Max ?
                     res : // Positive 24 bit numbers
                     int24Max - res; // Negative number
      }
      

      【讨论】:

        【解决方案5】:

        整数提升[conv.prom] 在移位表达式[expr.shift]/1 的操作数上执行。在您的情况下,这意味着您的unsigned char 类型的值将在&lt;&lt; 应用[conv.prom]/1 之前转换为int 类型。因此,C++ 标准保证操作数被“向上转换”。

        但是,标准只保证int 至少有 16 位。也不能保证unsigned char 正好有 8 位(它可能有更多)。因此,不能保证int 总是足够大来表示这些左移的结果。如果int 碰巧不够大,则生成的有符号整数溢出将调用未定义的行为[expr]/4int 很有可能在您的目标平台上具有 32 位,因此,最终一切正常。

        如果您需要使用有保证的固定位数,我通常建议使用fixed-width integer types,例如:

        std::int32_t interpret24bitAsInt32(const std::uint8_t* byteArray)
        {     
            return
                static_cast<std::int32_t>(
                    (std::uint32_t(byteArray[0]) << 24) | 
                    (std::uint32_t(byteArray[1]) << 16) | 
                    (std::uint32_t(byteArray[2]) <<  8)
                ) >> 8;
        }
        

        请注意,负值的右移当前是实现定义的[expr.shift]/3。因此,不能严格保证此代码最终会对负数执行符号扩展。但是,您的编译器需要记录右移负整数的确切作用[defns.impl.defined](即,您可以去确保它满足您的需要)。而且我从未听说过没有将负值的右移实现为实践中的算术移位的编译器。此外,it looks like C++20 将强制执行算术移位行为……

        【讨论】:

        • (std::int32_t(byteArray[0]) &lt;&lt; 24 未定义如果byteArray[0] &gt; 127,它会将1 移入int32_t 的符号位。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-23
        • 2011-02-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多