【问题标题】:How to check for signed integer overflow in C without undefined behaviour?如何在没有未定义行为的情况下检查 C 中的有符号整数溢出?
【发布时间】:2011-02-07 16:27:59
【问题描述】:

有 (1) 个:

// assume x,y are non-negative
if(x > max - y) error;

和(2):

// assume x,y are non-negative
int sum = x + y;
if(sum < x || sum < y) error;

哪个是首选或有更好的方法。

【问题讨论】:

  • 实际上,副本根本不是副本,它是在谈论具有明确定义的环绕语义的unsigned,而溢出有符号整数是C中未定义的行为。
  • sum &lt; xsum &lt; y 都不需要检查。
  • 这以前是作为How to detect integer overflow? 的副本关闭的,但这是关于unsigned,其中包装是明确定义的行为。签名int 更难,因为你不能只添加并然后检查它是否溢出,那已经是UB,因此编译器可以假设没有溢出!

标签: c integer-overflow


【解决方案1】:

整数溢出是 C 中“未定义行为”的典型示例(请注意,对无符号整数的操作永远不会溢出,它们被定义为回绕)。这意味着一旦你执行了x + y,如果它溢出,你就已经被水洗了。做任何检查都太晚了——你的程序可能已经崩溃了。把它想象成检查除以零 - 如果你等到执行除法之后再检查,已经太晚了。

所以这意味着方法(1)是唯一正确的方法。对于max,您可以使用来自&lt;limits.h&gt;INT_MAX

如果x 和/或y 可以为负数,那么事情就更难了 - 您需要以测试本身不会导致溢出的方式进行测试。

if ((y > 0 && x > INT_MAX - y) ||
    (y < 0 && x < INT_MIN - y))
{
    /* Oh no, overflow */
}
else
{
    sum = x + y;
}

【讨论】:

  • 顺便说一句,与其他替代解决方案相比,您能评论一下该解决方案的性能吗?
  • 将性能与不正确的解决方案进行比较毫无意义。您还想到了哪些其他正确的解决方案?
  • 另一种通常的方式似乎是转换为更广泛的类型。我不确定第三种选择,但肯定还有更多。
  • @Pacerier 如果您已经在使用平台上最大的类型,则无法转换为更宽的类型。
  • @Tuntable:每个编译器所做的都是可能导致恐怖的优化:blog.llvm.org/2011/05/…
【解决方案2】:

你真的只能用unsigned整数和算术检查溢出:

unsigned a,b,c;
a = b + c;
if (a < b) {
    /* overflow */
}

有符号整数溢出的行为在 C 中是未定义的,但在大多数机器上都可以使用

int a,b,c;
a = b + c;
if (c < 0 ? a > b : a < b) {
    /* overflow */
}

这不适用于使用任何饱和算法的机器

【讨论】:

  • 在有符号整数的事实之后检查溢出是不正确的。这是未定义的行为,因此编译器会很高兴地优化检查而不通过像-fwrapv 这样的开关来启用签名包装作为语言扩展。这不仅仅是跨架构的可移植性问题。
  • 也可以检查有符号整数的溢出。见securecoding.cert.org/confluence/display/c/…
  • 不,现代 gcc 破坏了您签名的 int 示例。 Signed integer not overflowing on ARM64?.
【解决方案3】:

您只需检查其中一项。如果 x + y 溢出,它将小于 x 和 y。因此:

int sum = x + y;
if (sum < x) error;

应该足够了。

以下网站有一堆关于整数溢出的东西:

http://www.fefe.de/intof.html

如果要处理负数,可以扩展:

int sum = x + y;
if (y >= 0) {
   if (sum < x) error;
} else {
   if (sum > x) error;
}

【讨论】:

  • 原发帖人指定了非负整数,但我添加了处理负数的代码。
  • 这是不正确的——一旦x + y 溢出,程序就会出现未定义的行为。您必须在实际执行溢出操作之前进行检查 - 就像整数除以零一样。
  • 如果有什么比这个答案更错误,那么可能是它链接到的页面。
  • 这对于 unsigned 来说是正确的,我认为这对这种情况非常有用。
猜你喜欢
  • 1970-01-01
  • 2017-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多