为什么这个多次递归会在一定数量的递归中失败?

时间:2016-11-14 02:44:22

标签: swift macos recursion

此代码将所有整数和number相加,但它会在Segmentation fault: 11或更大时与104829(或内存访问不良)崩溃。为什么呢?

import Foundation
func sigma(_ m: Int64) -> Int64 {
    if (m <= 0 ) {
        return 0
    } else {
        return m + sigma(m - 1)
    }
}
let number: Int64 = 104829
let answer = sigma(number)

nb: sigma(104828) = 5494507206

在带有8GB Ram的CoreDuo 2 Macbook Pro上的macOS 10.11终端上运行(这是相关的!)

1 个答案:

答案 0 :(得分:3)

您正在获得Stack Overflow。您可以使用getrlimit(2)/setrlimit(2)获取/设置当前进程的堆栈大小。以下是一个示例用法:

import Darwin // Unnecessary if you already have Foundation imported

func getStackByteLimit() -> rlimit? {
    var limits = rlimit()

    guard getrlimit(RLIMIT_STACK, &limits) != -1 else {
        perror("Error with getrlimit")
        return nil
    }

    return limits
}

func setStackLimit(bytes: UInt64) -> Bool {
    guard let max = getStackByteLimit()?.rlim_max else { return false }

    var limits = rlimit(rlim_cur: bytes, rlim_max: max)

    guard setrlimit(RLIMIT_STACK, &limits) != -1 else {
        perror("Error with setrlimit")
        return false
    }

    return true
}

默认情况下,它是8,388,608个字节,2,048字节的4,096个页面。

你的是一个无法进行尾调用优化的算法的教科书示例。递归调用的结果不是直接返回,而是用作加法的操作数。因此,编译器无法生成代码以在递归期间消除堆栈帧。他们必须留下来,以便跟踪最终需要完成的添加。可以使用累加器参数来改进此算法:

func sigma(_ m: Int64, acc: Int64 = 0) -> Int64 {
   if (m <= 0 ) {
       return acc
   } else {
       return sigma(m - 1, acc: acc + m)
   }
}

在此代码中,直接返回递归调用的结果。因此,编译器可以编写删除中间堆栈帧的代码。这应该可以防止堆栈溢出。

但实际上,你可以在恒定的时间内完成这项工作,而不需要任何递归的无意义:p

func sum(from start: Int64 = 0, to end: Int64) -> Int64 {
    let count = end - start + 1

    return (start * count + end * count) / 2
}

print(sum(to: 50))