用于安全整数转换的C ++模板

时间:2009-06-15 21:48:35

标签: c++ templates casting integer-overflow

我正在尝试编写一个C ++模板函数,它将在不同整数类型之间的强制转换中抛出整数溢出的运行时异常,具有不同的宽度,以及可能的有符号/无符号不匹配。出于这些目的,我不关心从浮点类型转换为整数类型,也不关心其他对象到对象的转换。我想这样做而不必写很多特殊的案例代码。这就是我目前所拥有的:

template< typename T, typename R > void safe_cast( const T& source, R& result )
{
    // get the maximum safe value of type R
    R rMax = (R) ~0;
    if ( rMax < 0 ) // R is a signed type
    {
        // assume that we're on an 8-bit twos-compliment machine
        rMax = ~( 0x80 << ( ( sizeof( R ) - 1 ) * 8 ) );
    }

    if ( ( source & rMax  ) != source )
    {
        throw new IntegerOverflowException( source );
    }

    result = static_cast<R>( source );
}

这是正确有效的吗?

编辑:由于各种原因,stl不可用,所以我不能使用std :: numeric_limits,Boost的任何东西都可以用完。

10 个答案:

答案 0 :(得分:12)

您可以使用std::numeric_limits模板以更优雅的方式获取任何基本类型的最小和最大安全值(以及大量其他信息),例如std::numeric_limits<T>::max()。您需要添加<limits>

参考:http://www.cplusplus.com/reference/std/limits/numeric_limits/

答案 1 :(得分:11)

提升是一种选择吗?如果是,请尝试boost::numeric_cast<>。它似乎提供了您正在寻找的特征。

答案 2 :(得分:7)

我认为现在这些都有效,无论你是否使用两个补码。请在使用前进行广泛测试。他们给出了以下结果。每一行都会导致一个断言失败(只需将它们更改为异常)

/* unsigned -> signed, overflow */
safe_cast<short>(UINT_MAX);

/* unsigned -> unsigned, overflow */
safe_cast<unsigned char>(ULONG_MAX);

/* signed -> unsigned, overflow */
safe_cast<unsigned long>(-1);

/* signed -> signed, overflow */
safe_cast<signed char>(INT_MAX);

/* always works (no check done) */
safe_cast<long>(INT_MAX);

// giving these assertion failures results
(type)f <= (type)is_signed<To>::v_max
f <= (To)-1
f >= 0
f >= is_signed<To>::v_min && f <= is_signed<To>::v_max

实现。首先检查整数排名的一些实用程序(排名较高的类型将能够包含具有较低排名的类型的值,给定相同的符号。以及一些促销工具,以便能够找出一个常见的,安全的类型(这将永远不会如果涉及无符号类型,则生成有符号类型,如果有符号类型将无法存储无符号类型的所有值。)

/* ranks */
template<typename> struct int_rank;
#define RANK(T, I) template<> struct int_rank<T> \
    { static int const value = I; }

RANK(char, 1); RANK(unsigned char, 1); RANK(signed char, 1); 
RANK(short, 2); RANK(unsigned short, 2);
RANK(int, 3); RANK(unsigned int, 3);
RANK(long, 4); RANK(unsigned long, 4);
#undef RANK

/* usual arith. conversions for ints (pre-condition: A, B differ) */
template<int> struct uac_at;
template<> struct uac_at<1> { typedef int type; };
template<> struct uac_at<2> { typedef unsigned int type; };
template<> struct uac_at<3> { typedef long type; };
template<> struct uac_at<4> { typedef unsigned long type; };

template<typename A, typename B>
struct uac_type { 
    static char (&f(int))[1];
    static char (&f(unsigned int))[2];
    static char (&f(long))[3];
    static char (&f(unsigned long))[4];
    typedef typename uac_at<sizeof f(0 ? A() : B())>::type type; 
};

/* signed games */
template<typename> struct is_signed { static bool const value = false; };
#define SG(X, TT) template<> struct is_signed<X> { \
    static bool const value = true;                \
    static X const v_min = TT##_MIN;               \
    static X const v_max = TT##_MAX;               \
}

SG(signed char, SCHAR); 
SG(short, SHRT); 
SG(int, INT); 
SG(long, LONG); 
#undef SG

template<> struct is_signed<char> { 
    static bool const value = (CHAR_MIN < 0); 
    static char const v_min = CHAR_MIN; // just in case it's signed...
    static char const v_max = CHAR_MAX;
};

转换模板利用它们来确定每种情况何时需要完成或不完成。

template<typename To, typename From, 
         bool to_signed = is_signed<To>::value, 
         bool from_signed = is_signed<From>::value,
         bool rank_fine = (int_rank<To>::value >= int_rank<From>::value)>
struct do_conv;

/* these conversions never overflow, like int -> int, 
 * or  int -> long. */
template<typename To, typename From, bool Sign>
struct do_conv<To, From, Sign, Sign, true> {
    static To call(From f) {
        return (To)f; 
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, false, false> {
    static To call(From f) {
        assert(f <= (To)-1);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, true, true> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        /* no need to check whether To's positive range will
         * store From's positive range: Because the rank is
         * fine, and To is unsigned. 
         * Fixes GCC warning "comparison is always true" */
        assert(f >= 0);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, true, false> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        assert(f >= 0 && (type)f <= (type)(To)-1);
        return (To)f;
    }
};

template<typename To, typename From, bool Rank>
struct do_conv<To, From, true, false, Rank> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        assert((type)f <= (type)is_signed<To>::v_max);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, true, true, false> {
    static To call(From f) {
        assert(f >= is_signed<To>::v_min && f <= is_signed<To>::v_max);
        return (To)f;
    }
};

template<typename To, typename From>
To safe_cast(From f) { return do_conv<To, From>::call(f); }

答案 3 :(得分:5)

你试过SafeInt吗?它是一个跨平台模板,可以对各种整数类型进行整数溢出检查。它可以在github上找到

答案 4 :(得分:3)

怎么样:

template< typename T, typename R > void safe_cast( const T& source, R& result )
{
    R temp = static_cast<R>( source );
    if (static_cast<T> (temp) != source
        || ( temp < 0 && source > 0)
        || ( temp > 0 && source < 0))
    {
        throw IntegerOverflowException( source );
    }
    result = temp;
}

然后你只是检查铸件是否有效。确保你找回你的开始,并且标志没有翻转。

编辑: 由于以下评论搞砸了,这里格式化了:

int myint (-1);
safe_cast( myint, mychar );
safe_cast( mychar, myuchar ); // Exception is thrown here
safe_cast( myuchar, myint );

从int到char的转换工作正常。从char转换为unsigned char会抛出异常(应该如此)。我这里没有看到问题。

答案 5 :(得分:1)

我是否正确假设在R签名的情况下你试图用除了最后一位之外的所有1填充rMax?如果是这种情况,那么你应该有0x80(1000 0000)而不是0x10(0001 0000)。

此外,您的功能看起来不支持来源的负数。

编辑:

这是一个稍微编辑过的版本,我已经测试过从ints转换为chars:

template< typename T, typename R >
void safe_cast( const T& source, R& result )
{
    // get the maximum safe value of type R
    R rMax = (R) ~0;
    if ( rMax < 0 ) // R is a signed type
    {
        // assume that we're on an 8-bit twos-compliment machine
    rMax = ( 0x80 << ( ( sizeof( R ) - 1 ) * 8 ) );
    if(source >= 0)
        rMax = ~rMax;
    }

    if ( (source >= 0 && ( source & rMax  ) != source) || (source < 0 && (source & rMax) != rMax) )
    {
        throw new IntegerOverflowException( source );
    }

    result = static_cast<R>( source );
}

编辑:修正错误。

答案 6 :(得分:1)

http://rrsd.com/blincubator.com/bi_library/safe-numerics

处考虑安全数字

此库提供所有C原始整数类型的插入替换。导致错误结果的C操作 - 包括铸造在被检测时被捕获。

答案 7 :(得分:1)

自发布此问题以来已有十多年了,我想要一个自包含并使用现代C ++(std::optionalconstexprtype_traits)的解决方案。这是我写的:

/// Cast integer of type "From" to integer of type "To", as long as it fits. If it doesn't
/// fit, return std::nullopt.
template<typename To, typename From>
constexpr std::optional<To> IntegerCast(From from) {
    static_assert(std::is_integral_v<From>, "IntegerCast only supports integers");
    static_assert(std::is_integral_v<To>, "IntegerCast only supports integers");
    static_assert(!std::is_same_v<To, bool>, "IntegerCast only supports integers");
    static_assert(!std::is_same_v<From, bool>, "IntegerCast only supports integers");

    constexpr bool fromSigned = std::is_signed_v<From>;
    constexpr bool toSigned = std::is_signed_v<To>;
    constexpr bool bothSigned = fromSigned && toSigned;
    constexpr bool bothUnsigned = !fromSigned && !toSigned;

    constexpr From fromMax = std::numeric_limits<From>::max();
    constexpr From fromMin = std::numeric_limits<From>::min();
    constexpr To toMax = std::numeric_limits<To>::max();
    constexpr To toMin = std::numeric_limits<To>::min();

    if constexpr (bothUnsigned) {
        using Widen = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (bothSigned) {
        using Widen = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else if (from < Widen(toMin)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (fromSigned && !toSigned) {
        using Widen =
                std::make_unsigned_t<std::conditional_t<(sizeof(From) > sizeof(To)), From, To>>;
        if (from < 0) {
            return std::nullopt;
        } else if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (!fromSigned && toSigned) {
        using Widen =
                std::make_unsigned_t<std::conditional_t<(sizeof(From) > sizeof(To)), From, To>>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    }
}

它带有GoogleTest中的测试套件

TEST(IntegerCast, Basics) {
    constexpr uint64_t large64 = 10000000000000000000ull;
    static_assert(IntegerCast<uint8_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(large64) == 10000000000000000000ull);
    static_assert(IntegerCast<int8_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int16_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int32_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int64_t>(large64) == std::nullopt);

    constexpr int64_t largeNegative64 = -5000000000000000000;
    static_assert(IntegerCast<uint8_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int8_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int16_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int32_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int64_t>(largeNegative64) == -5000000000000000000);

    constexpr uint64_t small64 = 1;
    static_assert(IntegerCast<uint8_t>(small64) == 1);
    static_assert(IntegerCast<uint16_t>(small64) == 1);
    static_assert(IntegerCast<uint32_t>(small64) == 1);
    static_assert(IntegerCast<uint64_t>(small64) == 1);
    static_assert(IntegerCast<int8_t>(small64) == 1);
    static_assert(IntegerCast<int16_t>(small64) == 1);
    static_assert(IntegerCast<int32_t>(small64) == 1);
    static_assert(IntegerCast<int64_t>(small64) == 1);

    constexpr int64_t smallNegative64 = -1;
    static_assert(IntegerCast<uint8_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<int8_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int16_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int32_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int64_t>(smallNegative64) == -1);
}

TEST(IntegerCast, Boundaries) {
    constexpr uint8_t maxUnsigned8 = 255;
    static_assert(IntegerCast<uint8_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint16_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint32_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint64_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int8_t>(maxUnsigned8) == std::nullopt);
    static_assert(IntegerCast<int16_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int32_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int64_t>(maxUnsigned8) == 255);

    constexpr uint8_t minUnisigned8 = 0;
    static_assert(IntegerCast<uint8_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint16_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint32_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint64_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int8_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int16_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int32_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int64_t>(minUnisigned8) == 0);

    constexpr int8_t maxSigned8 = 127;
    static_assert(IntegerCast<uint8_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint16_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint32_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint64_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int8_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int16_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int32_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int64_t>(maxSigned8) == 127);

    constexpr int8_t minSigned8 = -128;
    static_assert(IntegerCast<uint8_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<int8_t>(minSigned8) == -128);
    static_assert(IntegerCast<int16_t>(minSigned8) == -128);
    static_assert(IntegerCast<int32_t>(minSigned8) == -128);
    static_assert(IntegerCast<int64_t>(minSigned8) == -128);
}

TEST(IntegerCast, SameSizeDifferentSign) {
    constexpr uint8_t above = 200;
    static_assert(IntegerCast<int8_t>(above) == std::nullopt);

    constexpr uint8_t withinUnsigned = 100;
    static_assert(IntegerCast<int8_t>(withinUnsigned) == 100);

    constexpr int8_t withinSigned = 100;
    static_assert(IntegerCast<uint8_t>(withinSigned) == 100);

    constexpr int8_t below = -100;
    static_assert(IntegerCast<uint8_t>(below) == std::nullopt);
}

答案 8 :(得分:0)

我一定错过了什么,但这不是你想要的吗?:

// using a more cast-like prototype, if I may:
template<class to, class from> inline
to safe_cast(from f)
{
   to t = static_cast<to>(f);
   if ( t != f ) throw whatever; // no new!
   return t;
}

答案 9 :(得分:0)

我在sweet.hpp有一个名为 conv.hpp 的标题。它将测试所有整数类型的边界,并允许进行整数的字符串强制转换。

short a = to<short>(1337);
std::string b = to<std::string>(a);
long c = to<long>(b);