C++中的24位到32位转换

24-bit to 32-bit conversion in C++

本文关键字:32位 转换 24位 中的 C++      更新时间:2023-10-16

我需要在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

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

[conf.prom]7.6整体促销

1如果int可以表示源类型的所有值,则整数转换秩(7.15)小于int的秩的除boolchar16_tchar32_twchar_t之外的整数类型的prvalue可以转换为int类型的prvalue;否则,可以将源prvalue转换为类型为unsigned int的prvalue。

因此,是的,标准要求在求值之前将类型为unsigned char的移位运算符的自变量提升为int


也就是说,代码中的技术依赖于inta)是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答案中的技巧。也使用移位而不是乘法:

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;
}

测试用例

对于运算符算术,确实存在小于int的类型的Integral_production

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

在中

byteArray[0] << 24

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

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

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

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

负数的前导CCD_ 29保留在CCD_ 30中。

因此,在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
}

对移位表达式[expr.shift]/1的操作数执行积分提升[conv.promm]。在您的情况下,这意味着在应用<<[conv.prom]/1之前,unsigned char类型的值将转换为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。因此,不能严格保证此代码最终会对负数执行符号扩展。然而,您的编译器需要记录负整数的右移操作[dfns.impl.defined](即,您可以确保它能满足您的需要)。我从来没有听说过编译器在实践中不将负值的右移作为算术移位。此外,看起来C++20将强制执行算术移位行为…