繁体   English   中英

整数溢出是否会因内存损坏而导致未定义的行为?

[英]Does integer overflow cause undefined behavior because of memory corruption?

我最近读到C和C ++中的带符号整数溢出会导致未定义的行为:

如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义。

我目前正试图了解这里未定义行为的原因。 我认为这里发生了未定义的行为,因为当整数变得太大而无法适应底层类型时,整数开始操纵自身周围的内存。

所以我决定在Visual Studio 2015中编写一个小测试程序,用以下代码测试该理论:

#include <stdio.h>
#include <limits.h>

struct TestStruct
{
    char pad1[50];
    int testVal;
    char pad2[50];
};

int main()
{
    TestStruct test;
    memset(&test, 0, sizeof(test));

    for (test.testVal = 0; ; test.testVal++)
    {
        if (test.testVal == INT_MAX)
            printf("Overflowing\r\n");
    }

    return 0;
}

我在这里使用了一个结构来防止Visual Studio在调试模式下的任何保护问题,比如堆栈变量的临时填充等等。 无限循环应该导致test.testVal几次溢出,并且确实如此,除了溢出本身之外没有任何后果。

我在运行溢出测试时查看了内存转储,结果如下( test.testVal的内存地址为0x001CFAFC ):

0x001CFAE5  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001CFAFC  94 53 ca d8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

带内存转储的溢出整数

如你所见,int周围的内存不断溢出仍然“完好无损”。 我用类似的输出测试了几次。 从来没有任何关于溢出的int损坏的内存。

这里发生了什么? 为什么变量test.testVal周围的内存没有受损? 这怎么会导致未定义的行为?

我试图理解我的错误以及为什么在整数溢出期间没有内存损坏。

你误解了未定义行为的原因。 原因不是整数周围的内存损坏 - 它总是占据整数占用的相同大小 - 而是基础算术。

由于有符号整数不需要以2的补码进行编码,因此无法具体指导它们溢出时会发生什么。 不同的编码或CPU行为可能导致不同的溢出结果,包括例如由于陷阱导致的程序杀死。

与所有未定义的行为一样,即使您的硬件对其算法使用2的补码并定义了溢出规则,编译器也不受它们的约束。 例如,很长一段时间,GCC优化了任何只能在二进制补码环境中实现的检查。 例如, if (x > x + 1) f()要从优化代码中删除if (x > x + 1) f() ,因为有符号溢出是未定义的行为,这意味着它永远不会发生(从编译器的视图来看,程序永远不会包含产生未定义行为的代码),意思是x永远不会大于x + 1

标准的作者保留了未定义的整数溢出,因为某些硬件平台可能会陷入其后果可能无法预测的方式(可能包括随机代码执行和随之而来的内存损坏)。 尽管具有可预测的静音环绕溢出处理的二进制补充硬件在C89标准发布时已经成为标准(我已经检查了许多可重编程微机架构,零使用其他任何东西)标准的作者不希望阻止任何人在旧机器上生成C实现。

在实现普通的二进制补充静默环绕语义的实现上,代码就像

int test(int x)
{
  int temp = (x==INT_MAX);
  if (x+1 <= 23) temp+=2;
  return temp;
}

当100%可靠时,在传递INT_MAX值时返回3,因为向INT_MAX添加1将产生INT_MIN,当然小于23。

在20世纪90年代,编译器使用了这样的事实:整数溢出是未定义的行为,而不是被定义为二进制补码包装,以实现各种优化,这意味着溢出的计算的确切结果将是不可预测的,但是行为方面没有取决于确切的结果将留在轨道上。 给定上述代码的20世纪90年代的编译器可能会对它进行处理,好像在INT_MAX中添加1会产生一个大于INT_MAX的值,从而导致函数返回1而不是3,或者它可能像旧的编译器一样,产生3。在上面的代码中,这样的处理可以在许多平台上保存指令,因为(x + 1 <= 23)将等于(x <= 22)。 编译器在选择1或3时可能不一致,但生成的代码除了产生其中一个值之外不会执行任何操作。

然而,从那以后,对于编译器而言,使用标准的失败对于程序行为的任何要求变得更加时髦,以防整数溢出(由硬件的存在导致的失败,其后果可能是真正不可预测的)以证明编译器的合理性。在溢出的情况下完全脱离轨道启动代码。 现代编译器可能会注意到,如果x == INT_MAX,程序将调用未定义的行为,从而得出该函数永远不会传递该值的结论。 如果函数永远不会传递该值,则可以省略与INT_MAX的比较。 如果从x == INT_MAX的另一个翻译单元调用上述函数,则它可能因此返回0或2; 如果从同一个翻译单元中调用,效果可能会更奇怪,因为编译器会将其关于x的推断扩展回调用者。

关于溢出是否会导致内存损坏,在某些旧硬件上可能会有。 在现代硬件上运行的旧编译器上,它不会。 在超现代编译器中,溢出否定了时间和因果关系的结构,所以所有的赌注都是关闭的。 x + 1评估中的溢出可以有效地破坏先前与INT_MAX的比较所看到的x的值,使其表现得好像内存中的x值已被破坏。 此外,这种编译器行为通常会删除会阻止其他类型的内存损坏的条件逻辑,从而允许发生任意内存损坏。

未定义的行为未定义。 它可能会使您的程序崩溃。 它可能什么都不做。 它可能完全符合您的预期。 它可能会召唤鼻子恶魔。 它可能会删除您的所有文件。 当遇到未定义的行为时,编译器可以自由地发出它喜欢的任何代码(或者根本不发出代码)。

任何未定义行为的实例都会导致整个程序未定义 - 不仅仅是未定义的操作,因此编译器可以对程序的任何部分执行任何操作。 包括时间旅行: 未定义的行为可能导致时间旅行(除其他外,但时间旅行是最有趣的)

有许多关于未定义行为的答案和博客文章,但以下是我的最爱。 如果您想了解有关该主题的更多信息,我建议您阅读它们。

除了深奥的优化结果之外,您还必须考虑其他问题,即使您天真地希望生成非优化编译器的代码也是如此。

  • 即使您知道该体系结构是二进制补码(或其他),溢出操作可能不会按预期设置标志,因此像if(a + b < 0)这样的语句可能会采用错误的分支:给定两个大的正数,所以当加在一起时它溢出并且结果,所以二元补充纯粹主义者声称,是否定的,但加法指令可能实际上没有设置负标志)

  • 多步操作可能发生在比sizeof(int)更宽的寄存器中,而不是在每一步都被截断,因此像(x << 5) >> 5这样的表达式可能不会像你一样切断左边的五位假设他们愿意。

  • 乘法和除法运算可以使用辅助寄存器来获得产品和股息中的额外位。 如果乘法“不能”溢出,编译器可以自由地假设辅助寄存器为零(或负产品为-1)并且在分割之前不重置它。 因此像x * y / z这样的表达式可能会使用比预期更广泛的中间产品。

其中一些听起来像额外的准确性,但它的超精确性是预期的,无法预测或依赖,并且违反了你的心理模型“每个操作接受N位二进制补码操作数并返回最不重要的N下一次操作的结果位“

C ++标准未定义整数溢出行为。 这意味着C ++的任何实现都可以随意做任何事情。

在实践中,这意味着:对于实现者来说最方便的是什么。 由于大多数实现者将int视为二进制补码值,因此现在最常见的实现是两个正数的溢出和是一个负数,它与真实结果有一定关系。 这是一个错误的答案 ,标准允许这样做,因为标准允许任何内容。

有一种说法认为整数溢出应该被视为一个错误 ,就像整数除零一样。 '86架构甚至有INTO指令在溢出时引发异常。 在某些时候,该论点可能会获得足够的权重,使其成为主流编译器,此时整数溢出可能会导致崩溃。 这也符合C ++标准,它允许实现做任何事情。

您可以想象一种架构,其中数字以小端方式表示为以空字符结尾的字符串,零字节表示“数字结束”。 可以通过逐字节添加来完成添加,直到达到零字节。 在这样的体系结构中,整数溢出可能会用一个覆盖尾随零,从而使得结果看起来更远,更长并且可能在将来破坏数据。 这也符合C ++标准。

最后,正如其他一些回复中所指出的,大量的代码生成和优化取决于编译器对其生成的代码及其执行方式的推理。 在整数溢出的情况下,编译器完全合法(a)生成用于添加的代码,其在添加大的正数时给出负结果;以及(b)通过添加大的正数来知道其代码生成给出了积极的结果。 因此,例如

if (a+b>0) x=a+b;

可能,如果编译器知道ab都是正数,没有费心去执行测试,但无条件地将a添加到b并将结果放入x 在二进制补码机器上,这可能导致将负值放入x ,这明显违反了代码的意图。 这完全符合标准。

未定义int表示的值。 就像你想的那样,记忆中没有“溢出”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM