14

讨论中的程序尝试sum-of-first-n-natural-numbers使用recursion. 我知道这可以使用一个简单的公式来完成,n*(n+1)/2但这里的想法是使用recursion.

程序如下:

#include <stdio.h>

unsigned long int add(unsigned long int n)
{
    return (n == 0) ? 0 : n + add(n-1); 
}

int main()
{
    printf("result : %lu \n", add(1000000));
    return 0;
}

该程序运行良好,n = 100,000但当 的值n增加到1,000,000Segmentation fault (core dumped)

以下内容来自gdb消息。

Program received signal SIGSEGV, Segmentation fault.
0x00000000004004cc in add (n=Cannot access memory at address 0x7fffff7feff8
) at k.c:4

我的问题:

  1. recursion depthin是否有任何硬接线限制C?还是recursion depth取决于可用的堆栈内存?

  2. 程序收到 reSIGSEGV 信号的可能原因是什么?

4

4 回答 4

15

通常,限制将是堆栈的大小。每次调用函数时,都会吃掉一定数量的堆栈(通常取决于函数)。吃掉的量就是栈帧,函数返回时回收。当程序启动时,堆栈大小几乎是固定的,要么是由操作系统指定(并且通常可以在那里调整),要么是在程序中硬编码。

  • 一些实现可能有一种技术,可以在运行时分配新的堆栈段。但总的来说,他们没有。

  • 一些函数会以稍微更不可预测的方式消耗堆栈,例如当它们在那里分配一个可变长度数组时。

  • 某些函数可以编译为使用尾调用,以保留堆栈空间。有时您可以重写您的函数,以便所有调用(例如对自身的调用)作为它所做的最后一件事发生,并期望您的编译器对其进行优化。

函数每次调用到底需要多少栈空间并不是那么容易看出的,还要看编译器的优化程度。在您的情况下,一种便宜的方法是在&n每次调用时打印;n可能会在堆栈上(特别是因为程序需要获取它的地址 - 否则它可能在寄存器中),并且它的连续位置之间的距离将指示堆栈帧的大小。

于 2012-04-20T08:43:55.793 回答
7

1)预计栈的消耗会减少,写成尾递归优化。

gcc -O3 prog.c

#include <stdio.h>

unsigned long long int add(unsigned long int n, unsigned long long int sum){
    return (n == 0) ? sum : add(n-1, n+sum); //tail recursion form
}

int main(){
    printf("result : %llu \n", add(1000000, 0));//OK
    return 0;
}
于 2012-04-20T10:54:39.413 回答
5

C 中的递归深度没有理论上的限制。唯一的限制是您的实现,通常是有限的堆栈空间。
(请注意,C 标准实际上并不需要基于堆栈的实现。我不知道任何不基于堆栈的实际实现,但请记住这一点。)

SIGSEGV 可能由任意数量的事物引起,但超出您的堆栈限制是一种相对常见的情况。取消引用坏指针是另一回事。

于 2012-04-20T08:42:04.817 回答
3

C 标准没有定义函数调用的最小支持深度。如果确实如此,无论如何都很难保证,它会在第 1 节中的某个地方提到它5.2.4 Environmental limits

于 2012-04-20T09:10:57.033 回答