如何处理c++代码中可能出现的整数溢出?

(How) do you handle possible integer overflows in C++ code?

本文关键字:整数 溢出 何处理 处理 代码 c++      更新时间:2023-10-16

不时地,特别是在做一些代码库的64位构建时,我注意到有很多情况下整数溢出是可能的。最常见的情况是我这样做:

// Creates a QPixmap out of some block of data; this function comes from library A
QPixmap createFromData( const char *data, unsigned int len );
const std::vector<char> buf = createScreenShot();
return createFromData( &buf[0], buf.size() ); // <-- warning here in 64bit builds

问题是,std::vector::size()很好地返回size_t(在64位构建中为8字节),但该函数恰好取unsigned int(在64位构建中仍然只有4字节)。因此编译器会正确地发出警告。

如果可能的话,我尝试首先修复签名以使用正确的类型。然而,当我组合来自不同库的函数时,我经常遇到这个问题,我不能修改。不幸的是,我经常采用"好吧,没有人会做一个生成超过4GB数据的屏幕截图,所以为什么要这么做呢?"这样的推理,然后把代码改成

return createFromData( &buf[0], static_cast<unsigned int>( buf.size() ) );

使编译器关闭。然而,这感觉真的很邪恶。所以我一直在考虑有某种运行时断言,至少在调试构建中产生一个不错的错误,如:

assert( buf.size() < std::numeric_limits<unsigned int>::maximum() );

这已经好一点了,但是我想知道:你如何处理这种问题,即:整数溢出,这在实践中"几乎"是不可能的。我想这意味着它们不会发生在你身上,也不会发生在QA身上——但它们会在客户面前爆发。

如果您不能修复类型(因为您不能破坏库兼容性),并且您"确信"大小永远不会变得那么大,则可以使用boost::numeric_cast代替static_cast。如果值太大,将抛出异常。

当然,周围的代码必须对异常做一些模糊的明智的事情——因为它是一个"不期望发生"的条件,这可能意味着干净地关闭。

解决方案取决于上下文。在某些情况下,您知道数据在哪里来自,并且可以排除溢出:一个初始化的int例如,0并每秒递增一次,不会溢出在机器使用寿命内的任何时间。在这种情况下,您只需转换即可(或者允许隐式转换完成它的工作),不用担心。

另一种类型的情况非常相似:例如,在您的情况中,它是可能不合理的屏幕截图有更多的数据,可以由int表示,因此转换也是安全的。提供了数据确实来自于屏幕截图;在这种情况下,通常程序是对输入的数据进行验证,确保其满足要求您的约束下游,然后不做进一步的验证。

最后,如果你无法真正控制数据的来源,并且不能在进入时进行验证(至少不能对您的约束进行验证)下游),你被困在使用某种检查转换,在转换点立即验证

如果你将64位溢出的数字放入32位库中,你就打开了潘多拉的盒子——未定义的行为。

抛出异常。因为异常通常会在任何地方出现,所以你应该有合适的代码来捕获它。考虑到这一点,你不妨利用它。

错误消息令人不快,但它们比未定义的行为要好。

可以采用以下四种方式之一或组合使用:

  • 使用正确的类型
  • 使用静态断言
  • 使用运行时断言
  • 忽略直到受伤
通常最好的方法是使用正确的类型,直到你的代码变得丑陋,然后加入静态断言。对于这个目的,静态断言比运行时断言要好得多。

最后,当静态断言不起作用时(就像在你的例子中),你使用运行时断言——是的,它们会出现在客户面前,但至少你的程序的行为是可预测的。是的,客户不喜欢断言—他们开始恐慌("我们有错误!"全大写),但是如果没有断言,程序可能会出错,并且没有办法轻松地诊断问题。

我想到了一件事:因为我需要某种运行时检查(例如buf.size()的值是否超过unsigned int的范围只能在运行时进行测试),但我不想到处都有一百万assert()调用,我可以做一些像

template <typename T, typename U>
T integer_cast( U v ) {
    assert( v < std::numeric_limits<T>::maximum() );
    return static_cast<T>( v );
}

这样,我至少可以集中断言,并且

return createFromData( &buf[0], integer_cast<unsigned int>( buf.size() ) );

稍微好一点。也许我应该抛出一个异常(它确实非常异常!)而不是assert,通过回滚以前的工作并发出诊断输出或类似的方法,给调用者一个优雅地处理这种情况的机会。