如何避免在 C++ 中超出数据类型宽度的位运算

How to avoid bitwise operations outside the width of the data type in c++

为了实验起见,让我们有一个函数,它接受位掩码和偏移量,returns 掩码按偏移量移动。确定操作是否不会将位掩码的任何部分移动到超过数据类型宽度的性能友好的方法是什么?到目前为止,这是我尝试过的方法,但也许有更好的方法来检查它?

示例程序:(注意:我正在寻找适用于所有数据类型的解决方案,而不仅仅是 16 位整数)

#include <iostream>

using namespace std;

uint16_t TestFunc(uint16_t offset, uint16_t mask)
{
    if (offset >= sizeof(uint16_t) * 8)
        throw std::exception("Offset outside bounds");

    // find the index of the left-most bit in the mask
    int16_t maskLeftBitIndex = 0;
    uint16_t maskCopy = mask;

    while (maskCopy >>= 1)
        maskLeftBitIndex++;

    // check if the said left-most bit will be shifted past the width of uint16_t
    if (offset + maskLeftBitIndex >= sizeof(uint16_t) * 8)
        throw std::exception("Mask will end up outside bounds");

    return mask << offset;
}

int main()
{
    try
    {
        uint16_t test = TestFunc(15, 2);

        cout << "Bitmask value: " << test;
    }
    catch (std::exception& e)
    {
        cout << "Exception encountered: " << e.what();
    }

    return 0;
}

不确定这样会不会更快,但您可以检查前值和结束值是否设置了相同的位数

uint16_t TestFunc(uint16_t offset, uint16_t mask)
{
    if (offset >= std::numeric_limits<uint16_t>::digits)
        throw "Offset outside bounds (Possible Undefined Behavior)";
    
    uint16_t result = mask << offset;
    if(std::popcount(mask)!=std::popcount(result))
        throw "Offset outside bounds";
   
    return result;
}

这是我制作的示例的开头(未针对有符号整数进行测试)。

#include <cassert>
#include <cstdint>
#include <utility>
#include <stdexcept>

// instead of "real" exceptions, use return based exceptions
// kind of in the spirit of what Herb Sutter wants to do 
// with exceptions in the future too
enum class shift_result_exception
{
    none,
    invalid_arg,
    out_of_range
};

// struct to hold a return value and an "exception"
template<typename type_t>
struct shift_result_t
{
    shift_result_exception exception{ shift_result_exception::invalid_arg };

    type_t value{};

    operator type_t()
    {
        return value;
    }
};

// the actual shift logic.
template<typename type_t>
auto shift_left(const type_t value, const std::size_t shift)
{
    shift_result_t<type_t> result;

    // if caller wants to shift more places then the size of the type
    // this will be an invalid_arg result
    std::size_t type_size = sizeof(type_t) << 3; // = *8
    if (shift >= type_size) return result;

    // number of bits at front of value to check
    type_t mask = (1 << shift) - 1; 
    std::size_t mask_shift = (type_size - shift);
    // shift ones to the front of the type if any of the ones

    mask <<= mask_shift;

    // overlaps with a one of the value then the value cannnot be shifted
    // and the shifted value would go out of range
    if ((mask & value) != 0)
    {
        result.exception = shift_result_exception::out_of_range;
        return result;
    }

    // it is safe to shift.
    result.value = value << shift;
    result.exception = shift_result_exception::none;
    return result;
}

int main()
{
    assert(shift_left<int>(1, 1) == 2);
    assert(shift_left<std::uint8_t>(1, 7) == 128);
    assert(shift_left<std::uint8_t>(2, 7).exception == shift_result_exception::out_of_range);
    assert(shift_left<std::uint8_t>(1, 8).exception == shift_result_exception::invalid_arg);
    assert(shift_left<std::uint8_t>(3, 5) == 96);
    assert(shift_left<std::uint32_t>(1, 31).exception == shift_result_exception::none);
}