为啥无限递归会导致段错误

Posted

技术标签:

【中文标题】为啥无限递归会导致段错误【英文标题】:Why infinite recursion leads to seg fault为什么无限递归会导致段错误 【发布时间】:2010-06-03 10:02:39 【问题描述】:

为什么无限递归会导致段错误? 为什么堆栈溢出会导致段错误。 我正在寻找详细的解释。

int f()

  f();


int main()

  f();

【问题讨论】:

见Why does this C++ code-snippet segmentation fault?。 【参考方案1】:

每次调用 f() 时,都会增加堆栈的大小 - 这是存储返回地址的地方,因此程序知道 f() 完成时要去哪里。由于您永远不会退出 f(),因此每次调用堆栈将至少增加一个返回地址。一旦堆栈段已满,就会出现段错误。您将在每个操作系统中获得相似的结果。

【讨论】:

将尾调用优化为循环的编译器除外。但无论如何,你的回答是正确的,+1 来自我【参考方案2】:

Segmentation fault 是您的程序尝试访问不允许访问的内存位置时的一种情况。无限递归会导致您的堆栈增长。并成长。并成长。最终它会增长到溢出到操作系统禁止您的程序访问的内存区域的程度。那是您遇到分段错误的时候。

【讨论】:

我听说过用“堆栈-堆冲突”来描述这一点。 @Maxpm,这很有意义。堆栈和堆通常是朝着彼此增长的。因此,如果堆栈变得太大,它可能会溢出到堆中。【参考方案3】:

您的系统资源是有限的。他们是有限的。即使您的系统拥有整个地球上最多的内存和存储空间,infinite 也比您拥有的要大得多。现在记住这一点。

做某事“无限次”的唯一方法是“忘记”以前的信息。也就是说,你必须“忘记”以前做过的事情。否则,您必须记住之前发生的事情,并且需要存储一种或另一种形式(缓存、内存、磁盘空间、在纸上写下来……)——这是不可避免的。如果您要存储东西,那么可用空间是有限的。回想一下,无限比你拥有的要大得多。如果您尝试存储无限量的信息,您将耗尽存储空间。

当您使用递归时,每次递归调用都会隐式存储先前的信息。因此,在某些时候,如果您尝试执行无限次拍摄,您将耗尽您的存储空间。在这种情况下,您的存储空间是堆栈。堆栈是一块有限的内存。当您将其全部使用并尝试访问超出您所拥有的内容时,系统将生成一个异常,如果它尝试访问的内存被写保护,则最终可能导致段错误。如果它没有写保护,它将继续运行,覆盖上帝知道什么,直到它尝试写入不存在的内存,或者尝试写入其他一些写保护的内存,或者直到它破坏你的代码(在内存中)。

【讨论】:

【参考方案4】:

它仍然是一个 *** ;-)

问题是 C 运行时不像其他托管语言(例如 Java、Python 等)那样提供“工具化”,因此在为堆栈指定的空间之外编写而不是导致详细的异常只会引发较低级别的错误,其通用名称为“segmentation fault”。

这是出于性能原因,因为这些内存访问看门狗可以在硬件支持的帮助下设置,而开销很小或没有开销;我现在不记得确切的细节了,但通常是通过标记 MMU 页表或使用大部分过时的段偏移寄存器来完成的。

【讨论】:

【参考方案5】:

AFAIK:堆栈的末端受到进程无法访问的地址的保护。这可以防止堆栈在分配的数据结构上增长,并且比显式检查堆栈大小更有效,因为无论如何您都必须检查内存保护。

【讨论】:

【参考方案6】:

程序计数器或指令指针是一个寄存器,其中包含要执行的下一条指令的值。 在函数调用中,程序计数器的当前值被压入堆栈,然后程序计数器指向函数的第一条指令。从该函数返回后,旧值被弹出并分配给程序计数器。在无限递归中,值被一次又一次地压入并导致堆栈溢出。

【讨论】:

【参考方案7】:

本质上与缓冲区溢出原理相同;操作系统为堆栈分配固定数量的内存,当你用完(堆栈溢出)时,你会得到未定义的行为,在这种情况下意味着 SIGSEGV。

基本思路:

int stack[A_LOT];
int rsp=0;

void call(Func_p fn)
    
    stack[rsp++] = rip;
    rip = fn;
    

void retn()
    
    rip = stack[--rsp];
    

/*recurse*/
for(;;)call(somefunc);

最终 rsp 移动到堆栈的末尾,并且您尝试将下一个返回地址放入未分配的存储和您的程序 barfs 中。显然,实际系统比这复杂得多很多,但这可能(并且已经)占据了好几本大书。

【讨论】:

【参考方案8】:

在“低”级别,堆栈通过指针(堆栈指针)“维护”,保存在处理器寄存器中。这个寄存器指向内存,因为栈毕竟是内存。当您将值压入堆栈时,其“值”会递减(堆栈指针从较高地址移动到较低地址)。每次您输入一个函数时,都会从堆栈中“占用”一些空间(局部变量);此外,在许多体系结构中,对子程序的调用会将返回值压入堆栈(如果处理器没有特殊的寄存器堆栈指针,则可能会使用“普通”寄存器来达到此目的,因为堆栈即使在子程序可以用其他机制调用),因此堆栈至少会减少指针的大小(例如,4 或 8 个字节)。

在无限递归循环中,在最好的情况下,只有返回值会导致堆栈递减......直到它指向程序无法访问的内存。你会看到分段错误问题。

您可能会发现有趣的this page。

【讨论】:

一些 RISC 架构将返回地址存储在寄存器中……但这不允许递归,也不允许调用另一个子程序,除非它允许使用不同的寄存器;由于寄存器通常是有限的,所以最后人们会使用更方便的方法来允许递归......即堆栈......或一些其他机制(如MMIX),但是会消耗内存,可以以某种方式解决,所以同样的问题迟早会出现。

以上是关于为啥无限递归会导致段错误的主要内容,如果未能解决你的问题,请参考以下文章

为啥增加递归深度会导致堆栈溢出错误?

为啥这个非常简单的构造函数会导致段错误?

为啥这段代码不会导致无限循环?

为啥我的字符串分配会导致分段错误?

为啥释放内存会导致分段错误?

为啥在 MySQL 查询中添加 '*' 会导致语法错误?