在C/ c++中没有有效和可靠的方法来检测整数溢出
No useful and reliable way to detect integer overflow in C/C++?
不,这不是如何检测整数溢出?的重复。问题是一样的,但问题是不同的。
gcc编译器可以优化掉溢出检查(使用-O2),例如:
int a, b;
b = abs(a); // will overflow if a = 0x80000000
if (b < 0) printf("overflow"); // optimized away
gcc的人认为这不是一个bug。根据C标准,溢出是未定义的行为,它允许编译器执行任何操作。显然,任何都包含了永远不会发生溢出的假设。不幸的是,这允许编译器优化掉溢出检查。
在最近的一篇CERT论文中描述了检查溢出的安全方法。本文建议在添加两个整数之前执行以下操作:
if ( ((si1^si2) | (((si1^(~(si1^si2) & INT_MIN)) + si2)^si2)) >= 0) {
/* handle error condition */
} else {
sum = si1 + si2;
}
显然,当您想要确保结果有效时,必须在一系列计算中的每个+、-、*、/和其他操作之前执行类似的操作。例如,如果您想确保数组索引不越界。这太麻烦了,实际上没人做。至少我从未见过一个C/c++程序系统地做到这一点。
现在,这是一个基本问题:
在访问数组之前检查数组索引是有用的,但不可靠。
用CERT方法检查一系列计算中的每个操作是可靠的,但没有用。
结论:在C/c++中没有有效和可靠的方法来检查溢出!
我拒绝相信这是标准编写时的意图。
我知道有一些命令行选项可以解决这个问题,但这并不能改变这样一个事实,即我们对标准或当前对标准的解释存在根本问题。
我的问题是:当gcc人员允许他们优化溢出检查时,是对"未定义行为"的解释走得太远了,还是C/c++标准被破坏了?
添加注:对不起,你可能误解了我的问题。我不是在问如何解决这个问题——这个问题已经在其他地方得到了解答。我在问一个关于C标准的更基本的问题。如果没有有用和可靠的方法来检查溢出,那么语言本身就是可疑的。例如,如果我创建了一个带有边界检查的安全数组类,那么我应该是安全的,但如果边界检查可以优化掉,我就不安全了。
如果标准允许这种情况发生,那么要么标准需要修订,要么标准的解释需要修订。
新增注释2:这里的人们似乎不愿意讨论"未定义行为"这个可疑的概念。事实上,C99标准列出了191种不同的未定义行为(链接),这表明这是一个草率的标准。
许多程序员欣然接受"未定义行为"这一说法,即允许他们做任何事情,包括格式化硬盘。我认为这是一个问题,标准将整数溢出写入与数组边界外相同的危险类别。
为什么这两种"未定义行为"不同?因为:
许多程序依赖于整数溢出是良性的,但很少有程序依赖于在不知道存在什么的情况下在数组边界外写入。
实际上可以做一些糟糕的格式化你的硬盘(至少在一个不受保护的操作系统,如DOS),大多数程序员都知道这是危险的
当你把整型溢出放入危险的"anything goes"类别时,它允许编译器做任何事情,包括对它正在做的事情撒谎(在溢出检查被优化掉的情况下)
可以通过调试器发现写入数组边界以外的错误,但是无法通过优化取消溢出检查的错误,因为在调试时优化通常是关闭的。
在整数溢出的情况下,gcc编译器显然避免了"任意"策略。在许多情况下,它会避免优化,例如,除非它可以验证溢出是不可能的。由于某种原因,gcc的人已经认识到,如果他们遵循"一切正常"的策略,我们将会有太多的错误,但是他们对优化消除溢出检查的问题有不同的态度。
也许这不是讨论这种哲学问题的合适地方。至少,这里的大多数答案都离题了。有更好的地方讨论这个吗?
gcc开发人员在这里是完全正确的。当标准说行为是未定义的,这意味着在编译器上没有要求。
由于一个有效的程序不能做任何导致UB的事情(因为它将不再有效),编译器可以很好地假设UB没有发生。如果它仍然存在,编译器所做的任何事情都是可以的。
对于溢出问题,一个解决方案是考虑计算应该处理的范围。例如,在平衡我的银行账户时,我可以假设金额远低于10亿,因此32位整型可以工作。
对于您的应用程序域,您可能可以对在可能溢出的位置进行类似的估计。然后,您可以在这些点添加检查,或者选择其他数据类型(如果可用)。
int a, b;
b = abs(a); // will overflow if a = 0x80000000
if (b < 0) printf("overflow"); // optimized away
(你似乎在假设2s互补…让我们运行一下)
如果a
具有二进制模式(更准确地说,如果a
是INT_MIN
),谁说abs(a)
"溢出"?abs(int)
的Linux手册页说:
尝试取最大负整数的绝对值是没有定义的。
未定义并不一定意味着溢出。
所以,你的前提b
可以小于0,这在某种程度上是对"溢出"的测试,从一开始就是有根本缺陷的。如果你想测试,你不能在可能有未定义行为的结果上做测试——而是在操作之前做测试!
如果你关心这一点,你可以使用c++的用户定义类型(即类)来实现你自己的一组测试,围绕你需要的操作(或者找到一个已经这样做的库)。该语言不需要为此提供内置支持,因为它可以在这样的库中同样有效地实现,而使用的结果语义不变。这是c++最重要的功能之一。
问问你自己:你到底多久需要校验过的算术?如果经常需要,应该编写一个checked_int
类,重载通用操作符并将检查封装到该类中。在开源网站上分享实现的道具。
更好的是(有争议的),使用big_integer
类,这样首先就不会发生溢出。
请使用正确的b
类型:
int a;
unsigned b = a;
if (b == (unsigned)INT_MIN) printf("overflow"); // never optimized away
else b = abs(a);
Edit: C中的溢出测试可以安全地使用unsigned类型完成。无符号类型只是绕在算术上,有符号类型可以安全地转换为它们。所以你可以对它们做任何你喜欢的测试。在现代处理器上,这种转换通常只是对寄存器的重新解释,所以它不需要运行时成本。
- 检测win32服务创建和删除的最佳方法
- CoreCLR 中的检测探查器 - 将帮助程序程序集加载到 dotnet 进程的方法
- 通常使用什么方法来检测时间情况?
- 如何在窗口过程方法中检测击键?
- 有没有一种方法可以使用SFINAE来检测一个类型是否实现了给定的抽象基类
- 有什么方法可以检测我的类的对象是否在堆栈上创建
- 是否有内置方法来检测异常值?
- 使用信号检测子进程何时终止的最佳方法是什么?
- 检测 X11 与韦兰的有效方法,最好是使用 CMake
- 在Qt3D中进行碰撞检测的预期方法(或好方法)是什么?
- 有没有更好的方法来检测向量中一个项目的多次出现?
- 如何实现一致的方法来检测鼠标按钮被按住
- 在 c++ 生成器中检测绘制线的窗口坐标的最佳方法
- 无法使方法存在检测机制正常工作
- 检测特定像素上的某种颜色并在检测到后发送点击的最快方法是什么?
- 寻找一种在运行时检测 valgrind/memcheck 的方法,而无需包含 valgrind 标头
- 检测C 模板元图中的空隙方法
- 使用OpenCV检测图像中垂直文本的方法
- 是否有方法检测内联函数ODR违规
- 方法检测指针是否指向…确切的地点