为什么无限递归会导致seg错误

时间:2022-09-06 15:14:45

Why infinite recursion leads to seg fault ? Why stack overflow leads to seg fault. I am looking for detailed explanation.

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

int f()
{
  f();
}

int main()
{
  f();
}

8 个解决方案

#1


16  

Every time you call f(), you increase the size of the stack - that's where the return address is stored so the program knows where to go to when f() completes. As you never exit f(), the stack is going to increase by at least one return address each call. Once the stack segment is full up, you get a segfault error. You'll get similar results in every OS.

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

#2


14  

Segmentation fault is a condition when your program tries to access a memory location that it is not allowed to access. Infinite recursion causes your stack to grow. And grow. And grow. Eventually it will grow to a point when it will spill into an area of memory that your program is forbidden to access by the operating system. That's when you get the segmentation fault.

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

#3


4  

Your system resources are finite. They are limited. Even if your system has the most memory and storage on the entire Earth, infinite is WAY BIGGER than what you have. Remember that now.

您的系统资源是有限的。他们是有限的。即使你的系统在整个地球上拥有最多的记忆和存储空间,无限远比你拥有的还要大。现在请记住。

The only way to do something an "infinite number of times" is to "forget" previous information. That is, you have to "forget" what has been done before. Otherwise you have to remember what happened before and that takes storage of one form or another (cache, memory, disk space, writing things down on paper, ...)--this is inescapable. If you are storing things, you have a finite amount of space available. Recall, that infinite is WAY BIGGER than what you have. If you try to store an infinite amount of information, you WILL run out of storage space.

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

When you employ recursion, you are implicitly storing previous information with each recursive call. Thus, at some point you will exhaust your storage if you try to do this an infinite number of takes. Your storage space in this case is the stack. The stack is a piece of finite memory. When you use it all up and try to access beyond what you have, the system will generate an exception which may ultimately result in a seg fault if the memory it tried to access was write-protected. If it was not write-protected, it will keep on going, overwriting god-knows-what until such time as it either tries to write to memory that just does not exist, or it tries to write to some other piece of write protected memory, or until it corrupts your code (in memory).

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

#4


3  

It's still a * ;-)

它仍然是* ;-)

The thing is that the C runtime doesn't provide "instrumentalisation" like other managed languages do (e.g. Java, Python, etc.), so writing outside the space designated for the stack instead of causing a detailed exception just raises a lower level error, that has the generic name of "segmentation fault".

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

This is for performance reasons, as those memory access watchdogs can be set with help of hardware support with little or none overhead; I cannot remember the exact details now, but it's usually done by marking the MMU page tables or with the mostly obsolete segment offsets registers.

这是出于性能原因,因为这些内存访问看门狗可以借助硬件支持来设置,只需很少或不需要开销;我现在不记得确切的细节,但通常是通过标记MMU页表或大多数过时的段偏移寄存器来完成的。

#5


2  

AFAIK: The ends of the stack are protected by addresses that aren't accessible to the process. This prevents the stack from growing over allocated data-structures, and is more efficient than checking the stack size explicitly, since you have to check the memory protection anyway.

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

#6


1  

A program copunter or instruction pointer is a register which contains the value of next instruction to be executed. In a function call, the current value of program counter pushed into the stack and then program counter points to first instruction of the function. The old value is poped after returning from that function and assigned to program counter. In infinite recursion the value is pushed again and again and leads to the stack overflow.

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

#7


0  

It's essentially the same principle as a buffer overflow; the OS allocates a fixed amount of memory for the stack, and when you run out (stack overflow) you get undefined behavior, which in this context means a SIGSEGV.

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

The basic idea:

基本理念:

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);}

eventually rsp moves past the end of the stack and you try to put the next return address in unallocated storage and your program barfs. Obviously real systems are a lot more complicated than that, but that could (and has) take up several large books.

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

#8


0  

At a "low" level, the stack is "maintained" through a pointer (the stack pointer), kept in a processor register. This register points to memory, since stack is memory after all. When you push values on the stack, its "value" is decremented (stack pointer moves from higher addresses to lower addresses). Each time you enter a function some space is "taken" from the stack (local variables); moreover, on many architectures the call to a subroutine pushes the return value on the stack (and if the processor has no a special register stack pointer, likely a "normal" register is used for the purpose, since stack is useful even where subroutines can be called with other mechanisms), so that the stack is at least diminuished by the size of a pointer (say, 4 or 8 bytes).

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

In an infinite recursion loop, in the best case only the return value causes the stack to be decremented... until it points to a memory that can't be accessed by the program. And you see the segmentation fault problem.

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

You may find interesting this page.

你可能会发现这个页面很有趣。

#1


16  

Every time you call f(), you increase the size of the stack - that's where the return address is stored so the program knows where to go to when f() completes. As you never exit f(), the stack is going to increase by at least one return address each call. Once the stack segment is full up, you get a segfault error. You'll get similar results in every OS.

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

#2


14  

Segmentation fault is a condition when your program tries to access a memory location that it is not allowed to access. Infinite recursion causes your stack to grow. And grow. And grow. Eventually it will grow to a point when it will spill into an area of memory that your program is forbidden to access by the operating system. That's when you get the segmentation fault.

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

#3


4  

Your system resources are finite. They are limited. Even if your system has the most memory and storage on the entire Earth, infinite is WAY BIGGER than what you have. Remember that now.

您的系统资源是有限的。他们是有限的。即使你的系统在整个地球上拥有最多的记忆和存储空间,无限远比你拥有的还要大。现在请记住。

The only way to do something an "infinite number of times" is to "forget" previous information. That is, you have to "forget" what has been done before. Otherwise you have to remember what happened before and that takes storage of one form or another (cache, memory, disk space, writing things down on paper, ...)--this is inescapable. If you are storing things, you have a finite amount of space available. Recall, that infinite is WAY BIGGER than what you have. If you try to store an infinite amount of information, you WILL run out of storage space.

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

When you employ recursion, you are implicitly storing previous information with each recursive call. Thus, at some point you will exhaust your storage if you try to do this an infinite number of takes. Your storage space in this case is the stack. The stack is a piece of finite memory. When you use it all up and try to access beyond what you have, the system will generate an exception which may ultimately result in a seg fault if the memory it tried to access was write-protected. If it was not write-protected, it will keep on going, overwriting god-knows-what until such time as it either tries to write to memory that just does not exist, or it tries to write to some other piece of write protected memory, or until it corrupts your code (in memory).

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

#4


3  

It's still a * ;-)

它仍然是* ;-)

The thing is that the C runtime doesn't provide "instrumentalisation" like other managed languages do (e.g. Java, Python, etc.), so writing outside the space designated for the stack instead of causing a detailed exception just raises a lower level error, that has the generic name of "segmentation fault".

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

This is for performance reasons, as those memory access watchdogs can be set with help of hardware support with little or none overhead; I cannot remember the exact details now, but it's usually done by marking the MMU page tables or with the mostly obsolete segment offsets registers.

这是出于性能原因,因为这些内存访问看门狗可以借助硬件支持来设置,只需很少或不需要开销;我现在不记得确切的细节,但通常是通过标记MMU页表或大多数过时的段偏移寄存器来完成的。

#5


2  

AFAIK: The ends of the stack are protected by addresses that aren't accessible to the process. This prevents the stack from growing over allocated data-structures, and is more efficient than checking the stack size explicitly, since you have to check the memory protection anyway.

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

#6


1  

A program copunter or instruction pointer is a register which contains the value of next instruction to be executed. In a function call, the current value of program counter pushed into the stack and then program counter points to first instruction of the function. The old value is poped after returning from that function and assigned to program counter. In infinite recursion the value is pushed again and again and leads to the stack overflow.

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

#7


0  

It's essentially the same principle as a buffer overflow; the OS allocates a fixed amount of memory for the stack, and when you run out (stack overflow) you get undefined behavior, which in this context means a SIGSEGV.

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

The basic idea:

基本理念:

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);}

eventually rsp moves past the end of the stack and you try to put the next return address in unallocated storage and your program barfs. Obviously real systems are a lot more complicated than that, but that could (and has) take up several large books.

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

#8


0  

At a "low" level, the stack is "maintained" through a pointer (the stack pointer), kept in a processor register. This register points to memory, since stack is memory after all. When you push values on the stack, its "value" is decremented (stack pointer moves from higher addresses to lower addresses). Each time you enter a function some space is "taken" from the stack (local variables); moreover, on many architectures the call to a subroutine pushes the return value on the stack (and if the processor has no a special register stack pointer, likely a "normal" register is used for the purpose, since stack is useful even where subroutines can be called with other mechanisms), so that the stack is at least diminuished by the size of a pointer (say, 4 or 8 bytes).

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

In an infinite recursion loop, in the best case only the return value causes the stack to be decremented... until it points to a memory that can't be accessed by the program. And you see the segmentation fault problem.

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

You may find interesting this page.

你可能会发现这个页面很有趣。