繁体   English   中英

C++ 24 位到 32 位转换

[英]24-bit to 32-bit conversion in C++

我需要在 C++ 中将 24 位整数(2s 补码)转换为 32 位整数。 在这里找到了一个解决方案,如下所示

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

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

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

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) < 4时才需要int32_t ,否则会发生默认的整数提升为int

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

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

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

如果您需要使用有保证的固定位数,我通常建议使用固定宽度的整数类型,例如:

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]右移负整数究竟做了什么(即,您可以去确保它满足您的需要)。 而且我从未听说过编译器在实践中不会将负值右移作为算术移位来实现。 此外,看起来C++20 将强制要求算术移位行为......

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

[conv.prom] 7.6 积分促销

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

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


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

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

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_tint32_t转换的行为。 如果你想进入那个兔子洞,玩得开心,但要注意

或者,更简单:使用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;
}

测试用例

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

所以假设sizeof(char) < sizeof(int)

byteArray[0] << 24

byteArray在促进int和你做位移位int

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

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

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

负数的前导1保留在neg >> 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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM