如何安全地偏移位而没有未定义的行为?

时间:2016-09-22 14:16:55

标签: c++ std-bitset

我正在编写一个函数,将bitset转换为int / uint值,考虑到bitset可能比目标类型的位数少。

这是我写的函数:

template <typename T,size_t count> static T convertBitSetToNumber( const std::bitset<count>& bitset )
{
    T result;
    #define targetSize (sizeof( T )*CHAR_BIT)
    if ( targetSize > count )
    {
        // if bitset is 0xF00, converting it as 0x0F00 will lose sign information (0xF00 is negative, while 0x0F00 is positive)
        // This is because sign bit is on the left.
        // then, we need to add a zero (4bits) on the right and then convert 0xF000, later, we will divide by 16 (2^4) to preserve sign and value

        size_t missingbits = targetSize - count;

        std::bitset<targetSize> extended;
        extended.reset(); // set all to 0
        for ( size_t i = 0; i != count; ++i )
        {
            if ( i < count )
                extended[i+missingbits] = bitset[i];
        }

        result = static_cast<T>( extended.to_ullong() );

        result = result >> missingbits;

        return result;
    }
    else
    {
        return static_cast<T>( bitset.to_ullong() );
    }
}

&#34;测试程序&#34;:

uint16_t val1 = Base::BitsetUtl::convertBitSetToNumber<uint16_t,12>( std::bitset<12>( "100010011010" ) );
// val1 is 0x089A
int16_t val2 = Base::BitsetUtl::convertBitSetToNumber<int16_t,12>( std::bitset<12>( "100010011010" ) );
// val2 is 0xF89A

注意:请参阅与Ped7g的评论/交换,上面的代码是正确的并保留位符号,并对有符号或无符号位进行12-> 16位转换。但是如果你正在研究如何在签名对象上将0xABC0偏移到0x0ABC,答案可以帮助你,所以我不会删除这个问题。

使用uint16作为目标类型时,请参阅该程序,如下:

uint16_t val = 0x89A0; // 1000100110100000
val = val >> 4;        // 0000100010011010

但是,使用int16_t时失败,因为0x89A0 >> 40xF89A而不是预期0x089A

int16_t val = 0x89A0; // 1000100110100000
val = val >> 4;       // 1111100010011010

我不明白为什么&gt;&gt;运算符有时会插入0,有时是1.我无法找到如何安全地执行函数的最终操作(result = result >> missingbits;在某些时候必定是错误的...)

4 个答案:

答案 0 :(得分:4)

这称为arithmetic shifting。在签名类型上,最重要的位是符号位。向右移动负值时,高位设置为1,结果仍为负数。 (结果是除以2 n ,其中n是移位的位数,向负无穷大舍入)。

为避免这种情况,请使用无符号类型。移动它们使用logical shifting,它将高位设置为0.

更改此行:

result = result >> missingbits;

result = static_cast<T>(static_cast<uintmax_t>(result) >> missingbits);

uintmax_t是编译器支持的最大宽度无符号整数类型)

或使用std::make_unsigned作为Joachim Pileborg在他的回答中写道。

答案 1 :(得分:4)

因为移位是一个算术运算,操作数提升为int,这将进行符号扩展。

即。将带符号的16位整数(int16_t0x89a0提升为32位有符号整数(int)会使该值变为0xffff89a0,这是一个值移位。

参见例如this arithmetic operation conversion reference了解更多信息。

您应该将变量(或值)转换为无符号整数(例如uint16_t):

val = static_cast<uint16_t>(val) >> 4;

如果该类型不是真的知道,就好像它是一个模板参数,那么你可以使用std::make_unsigned

val = static_cast<typename std::make_unsigned<T>::type>(val) >> 4;

答案 2 :(得分:1)

如上所述,当您的类型为signed时,>>运算符正在执行算术移位。因此,除了上面建议的解决方案之外,如果您需要进行逻辑转换,您可以随时使用mask,如下所示:

    int mask = 1 << (targetSize-missingbits-1);
    mask |= mask - 1;
    result = (result >> missingbits) & mask;

在这种情况下,mask将为您提供missingbits MSB&#39; 0和其他1。在您的情况下,4个MSB将为0,其余为1。然后,执行&操作会重置missingbits中的第一个result,这就是您所需要的:

0xF89A & 0x0FFF = 0x089A

看到它正常工作live-example

答案 3 :(得分:1)

带循环的原始代码对我来说对我来说有点复杂,我会这样写(我的意思是作为第二个选项,在我以某种方式莫名其妙地无法完全避免使用#include <bitset> #include <climits> template <typename T,size_t count> static T convertBitSetToNumber( const std::bitset<count>& bitset ) { constexpr size_t targetSize = sizeof( T )*CHAR_BIT; if (targetSize == count) return static_cast<T>(bitset.to_ullong()); if (targetSize < count) return static_cast<T>(bitset.to_ullong() >> (count - targetSize)); return static_cast<T>(bitset.to_ullong() << (targetSize - count)) >> (targetSize - count); } // Example test producing from 0x089A bitset unsigned/signed values: // 16b: 89a f89a | 8b: 89 89 | 32b: 89a fffff89a #include <iostream> int main() { const std::bitset<12> testBitset("100010011010"); std::hex(std::cout); std::cout << convertBitSetToNumber<uint16_t,12>( testBitset ) << std::endl; std::cout << convertBitSetToNumber<int16_t,12>( testBitset ) << std::endl; std::cout << (0xFF & convertBitSetToNumber<uint8_t,12>( testBitset )) << std::endl; std::cout << (0xFF & convertBitSetToNumber<int8_t,12>( testBitset )) << std::endl; std::cout << convertBitSetToNumber<uint32_t,12>( testBitset ) << std::endl; std::cout << convertBitSetToNumber<int32_t,12>( testBitset ) << std::endl; } 和模板之后,对于像数据位大小调整这样简单的事情,首先):

# /usr/local/etc/mongod.conf
net:
  bindIp: 127.0.0.1