问题 没有有用和可靠的方法来检测C / C ++中的整数溢出?


不,这不是重复的 如何检测整数溢出?。问题是一样的,但问题是不同的。


gcc编译器可以优化掉溢出检查(使用-O2),例如:

int a, b;
b = abs(a);                     // will overflow if a = 0x80000000
if (b < 0) printf("overflow");  // optimized away

gcc人认为这不是一个错误。根据允许编译器执行的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 ++标准被破坏时,gcc人员是否对“未定义行为”的解释太过分了?

补充说明: 对不起,您可能误解了我的问题。我不是问如何解决这个问题 - 这已经得到了回答 别处。我在问一个关于C标准的更基本的问题。如果没有有用且可靠的方法来检查溢出,那么语言本身就是可疑的。例如,如果我使用边界检查创建一个安全的数组类,那么我应该是安全的,但是如果边界检查可以被优化掉的话我不会。

如果标准允许这样做,则标准需要修订或标准需求修订的解释。

补充说明2: 这里的人们似乎不愿意讨论“未定义行为”的可疑概念。事实上,C99标准列出了191种不同的未定义行为(链接)是标准草率的标志。

许多程序员都乐于接受“未定义的行为”赋予许可做任何事情的声明,包括格式化硬盘。我认为标准将整数溢出放入与写入数组边界相同的危险类别是一个问题。

为什么这两种“未定义的行为”不同?因为:

  • 许多程序依赖于整数溢出是良性的,但是当你不知道那里有什么时,很少有程序依赖于写出数组边界。

  • 实际上在数组边界外写 能够 做一些像格式化硬盘一样糟糕的事情(至少在DOS等不受保护的操作系统中),并且大多数程序员都知道这很危险。

  • 当你将整数溢出放入危险的“任何事情”类别时,它允许编译器做任何事情,包括说谎它正在做什么(在优化溢出检查的情况下)

  • 使用调试器可以找到诸如写入数组边界之类的错误,但是优化掉溢出检查的错误不能,因为优化通常在调试时关闭。

  • 在整数溢出的情况下,gcc编译器明显地避免了“任何事情”政策。在许多情况下,它避免优化,例如一个循环,除非它可以验证溢出是不可能的。出于某种原因,gcc人已经认识到如果他们遵循“任何事情”政策,我们会有太多错误,但他们对优化掉溢出检查的问题有不同的态度。

也许这不是讨论这些哲学问题的正确场所。至少,这里的大多数答案都是不合适的。有没有更好的地方来讨论这个?


3811
2017-07-28 08:42


起源

你在这里混淆了两个根本不同的东西 - 数组溢出检查,除了调试之外很少有用,以及算术溢出检查, 是 经常有用。 - Konrad Rudolph
“我拒绝相信” - 这是你的号召,但如果你拒绝相信真实的话,你就会遇到困难。对于可能溢出的计算,有符号整数类型在C或C ++中没有用。这是标准旨在避免在机器架构上施加开销的结果,这些架构的“自然”行为与您或我认为的行为应该是什么不同。这种后果被认为值得付出以避免执行负担。 - Steve Jessop
arry边界的计算是 特别 适用于无符号算术,因为您事先知道不应该得到负面的最终结果。 - MSalters
“如果没有有用和可靠的检查溢出的方法,那么语言本身就是可疑的” - 那么这个语言对你的定义是不确定的。如果您没有找到有用的语言,请不要使用它(或仅使用提供额外溢出保证的实现和命令行选项)。可能你与C标准委员会分手的地方是你认为在操作无用之前检查溢出,他们认为不是。可能你也认为无符号类型是无用的,但它们没有。 - Steve Jessop


答案:


问问自己:你多久需要一次检查算术?如果你经常需要,你应该写一个 checked_int 重载公共运算符并将检查封装到此类中的类。在开源网站上分享实施的道具。

更好(可以说),使用a big_integer 类,以便溢出不会发生在第一位。


4
2017-07-28 08:50





gcc开发人员在这里完全正确。当标准表明行为未定义时,这意味着确实存在 没有 对编译器的要求。

由于有效的程序不能做任何导致UB的事情(因为它不再有效),编译器可以很好地假设UB不会发生。如果它仍然存在,编译器所做的任何事情都可以。

对于溢出问题,一种解决方案是考虑caclulations应该处理的范围。例如,在平衡我的银行账户时,我可以假设金额远低于10亿,因此32位的int将起作用。

对于您的应用程序域,您可以准确地进行类似的估计 哪里 溢出是可能的。然后,您可以在这些点添加检查或选择其他数据类型(如果可用)。


4
2017-07-28 08:51



“例如,在平衡我的银行账户时,我可以假设金额远远低于10亿,因此32位的int将起作用。” - 并且具有以下优势:在通货膨胀过高的情况下,您可以确保修复自己的代码。您 真 在过度通货膨胀期间不想失业。 - Steve Jessop
这种假设恰恰是造成Y2K问题的原因。 - Graham Borland
@Graham - 不,不是。如果进行数值计算,则必须知道应用程序的预期域。如果域名发生变化,您将不得不重新评估。或者你已经在为Y10K问题做准备了吗? - Bo Persson
这有点真实。思考,“我将使用固定大小的类型/字段,因为它更快,永远不会失去内存,并且工作提供我处理的数字范围是有限的”,这种假设意味着Y2k ,Y10k和1000%的通货膨胀都会导致软件问题。 Y2k和Y10k问题之间的区别只是在60年代,当人们开始编写大量商业软件时,他们不知道他们的软件,它的惯例仍然在Y2K中使用。现在我们知道它是,并且不知道我们的软件和约定是否将在Y10K中使用。 - Steve Jessop
哦,另一个不同之处在于我更有信心我将在8000年内死去,而不是他们在40岁时死去。因此,即使我和他们一样错,也不会有尴尬的余地是。 - Steve Jessop


int a, b;
b = abs(a); // will overflow if a = 0x80000000
if (b < 0) printf("overflow");  // optimized away 

(你似乎假设2s补充...让我们一起运行)

谁说 abs(a) 如果“溢出” a 有二进制模式(更确切地说,如果 a 是 INT_MIN)? Linux手册页 abs(int) 说:

尝试取最负整数的绝对值未定义。

未定义并不一定意味着溢出。

所以,你的前提是 b 可能永远不会超过0,并且这在某种程度上是对“溢出”的测试,从一开始就存在根本性的缺陷。如果你想测试,你不能对可能有未定义行为的结果这样做 - 在操作之前做它!

如果您关心这一点,您可以使用C ++的用户定义类型(即类)来围绕您需要的操作实现您自己的一组测试(或找到已经执行此操作的库)。该语言不需要内置的支持,因为它可以在这样的库中同样有效地实现,结果使用的语义不变。这是基本的力量,是C ++的伟大之处。


3
2017-07-28 09:01





只需使用正确的类型 b

int a;
unsigned b = a;
if (b == (unsigned)INT_MIN) printf("overflow");  // never optimized away
else b = abs(a);

编辑: 使用无符号类型可以安全地测试C中的溢出。无符号类型只包含算术和签名类型安全地转换为它们。所以你可以对你喜欢的那些进行任何测试。在现代处理器上,这种转换通常只是对寄存器的重新解释,因此无需运行时成本。


2
2017-07-28 08:50



我从来没有听说过 UINT_MIN 之前 - 是不是只有0? - Kerrek SB
确实没有意义,演员阵容也很奇怪。此外,它可能不是预期的测试; 2s补充 INT_MIN 导致溢出将等于 UINT_MAX/2 + 1。所以我认为预期的测试是 if (b==unsigned(INT_MAX)。但为什么不清楚 if (a==INT_MAX) ? - MSalters
@A Fog,测试C中的溢出可以安全地完成 unsigned 类型。无符号类型只包含算术和签名类型安全地转换为它们。所以你可以对你喜欢的那些进行任何测试。在现代处理器上,这种转换通常只是对寄存器的重新解释,所以它是免费的。 - Jens Gustedt
@Kerrek,@ MSalters, INT_MIN, 确实。我只是把它放在OP的方式上,以显示溢出测试的位置。 - Jens Gustedt
同意。在C / C ++中检查溢出的正确方法是不确定的算法。有一个原因可以指定它包含在标准中! - Demi