堆栈溢出是否会导致除分段错误之外的其他内容?

时间:2018-06-06 19:43:03

标签: c++ c segmentation-fault stack-overflow

在一个已编译的程序中(假设是C或C ++,但我想这个问题可以扩展到任何带有调用堆栈的非VM语言) - 通常当你溢出堆栈时,you get a segmentation fault:< / p>

  

堆栈溢出是[a]原因,结果就是分段错误。

但总是这样吗?堆栈溢出是否会导致其他类型的程序/操作系统行为?

我也在问非Linux,非Windows操作系统和非X86硬件。 (当然,如果你没有硬件内存保护或操作系统支持(例如MS-DOS)那么就没有分段错误;我问的是可以获取的情况分段错误但其他事情发生了。)

注意:假设除了堆栈溢出之外,程序是有效的,并且不会尝试访问超出其边界的数组,取消引用无效指针等。

4 个答案:

答案 0 :(得分:31)

是的,即使在标准操作系统(Linux)和标准硬件(x86)上也是如此。

void f(void) {
    char arr[BIG_NUMBER];
    arr[0] = 0; // stack overflow
}

请注意,在x86上,堆栈会逐渐减少,因此我们将分配到数组的开头以触发溢出。通常的免责声明适用......确切的行为取决于比本答案中讨论的更多因素,包括C编译器的详细信息。

如果BIG_NUMBER刚刚大到足以溢出,您将遇到堆栈保护并获得分段错误。这就是堆栈保护的用途,它可以小到单个4 KiB页面(但不小,在Linux 4.12之前使用这个4 KiB大小)或者它可以更大(Linux 4.12上默认为1 MiB) ,见mm: large stack guard gap),但总是有一些特殊的尺寸。

如果BIG_NUMBER足够大,溢出可以跳过堆栈保护并落在其他一块内存上,可能是有效的内存。这可能会导致程序运行不正常但不会崩溃,这基本上是最糟糕的情况:我们希望我们的程序在错误时崩溃,而不是做一些无意的事情。

答案 1 :(得分:7)

有一件事是当你溢出堆栈时在运行时会发生什么,这可能是很多事情。包括但不仅限于;分段错误,覆盖任何溢出的变量,导致非法指令,什么都没有,等等。 &#34; old&#34;经典论文Smashing The Stack For Fun And Profit描述了人们可以拥有的很多方式,并且有趣的#34;这个东西。

另一件事是在编译时会发生什么。在C和C ++中,超出数组或超出堆栈大小的写入是未定义行为,当程序包含UB 任何地方时,编译器基本上可以自由地执行它想要的任何内容 任何部分程序。现代编译器在利用UB进行优化时变得非常积极 - 通常假设UB从未发生过,导致他们只是删除包含UB的代码或导致分支始终或永远不会被占用,因为替代方案会导致UB。有时,编译器会引入time travelcall a function that was never called in the source code以及许多其他可能导致实际上令人困惑的运行时行为的事情。

另见:

What Every C Programmer Should Know About Undefined Behavior #1/3

What Every C Programmer Should Know About Undefined Behavior #2/3

What Every C Programmer Should Know About Undefined Behavior #3/3

A Guide to Undefined Behavior in C and C++, Part 1

A Guide to Undefined Behavior in C and C++, Part 2

A Guide to Undefined Behavior in C and C++, Part 3

答案 2 :(得分:6)

其他答案已经很好地涵盖了PC方面。我将谈谈嵌入式世界中的一些问题。

嵌入式代码确实有类似于段错误的东西。代码存储在某种非易失性存储器中(这些天通常是闪存,但过去是某种ROM或PROM)。写这个需要特殊的操作来设置它;正常的内存访问可以从中读取但不能写入。此外,嵌入式处理器通常在其存储器映射中存在较大间隙。如果处理器获得对只读存储器的写请求,或者如果它获得对物理上不存在的地址的读或写请求,则处理器通常会抛出硬件异常。如果连接了调试器,则可以检查系统状态以查找出现问题的情况,如核心转储。

但是不能保证堆栈溢出会发生这种情况。堆栈可以放在RAM中的任何位置,这通常与其他变量并列。堆栈溢出的结果通常是破坏这些变量。

如果你的应用程序也使用堆(动态分配),那么通常会分配一段内存,其中堆栈从该部分的底部开始向上扩展,堆从该部分的顶部开始并向下扩展。显然,这意味着动态分配的数据将成为第一个受害者。

如果您不走运,您可能甚至都没有注意到它何时发生,然后您需要弄清楚为什么您的代码行为不正确。在最具讽刺意味的情况下,如果被覆盖的数据是一个指针,那么当指针试图访问无效内存时,你仍然可能会得到硬件异常 - 但这将是堆栈溢出后的一段时间,而自然的假设通常是它& #39;您的代码中存在错误。

嵌入式代码有一个共同的模式来处理这个问题,即#34; watermark&#34;通过将每个字节初始化为已知值来实现堆栈。有时编译器可以这样做;或者有时您可能需要在main()之前在启动代码中自己实现它。您可以从堆栈的末尾回头查找它不再设置为此值的位置,此时您知道堆栈使用的高水位标记;或者如果它都是不正确的,那么你知道你有溢出。嵌入式应用程序通常(以及良好实践)将其作为后台操作连续轮询,并且能够将其报告以用于诊断目的。

为了跟踪堆栈使用情况,大多数公司都会设置一个可接受的最坏情况余量来避免溢出。这通常在75%到90%之间,但总会有一些备用。这不仅有可能存在您还没有看到的更糟糕的最坏情况,而且当需要添加使用更多堆栈的新代码时,它还使未来开发的生活更轻松。

答案 3 :(得分:3)

Stackoverflow是程序many reasonsundefined behavior之一。在这种情况下,您可以获得预期的结果或分段错误,或者您的硬盘可能被擦除等。不要指望任何已定义的行为,因为它是未定义的行为。