【问题标题】:Bailing early from a recursive function从递归函数中提早释放
【发布时间】:2021-03-23 04:47:52
【问题描述】:

我正在玩一些递归函数,下面是我用来查看数组是否包含值的内容:

bool val_in_array_r(int val, int array[], size_t size)
{
    if (!size)
        return false;
    else
        return (val == *array)|| val_in_array_r(val, array+1, size-1);
}

我在这里有一个问题,我可以有一个包含十亿个条目的数组,第 10,000 个返回 true。而不必继续并获得:

  • false || false || ... || true || false || false || ...

如果遇到情况,有没有办法提前退出?或者这不是递归函数可以做的事情?

在我看来(理论上)做这样的事情需要超低级,因为它需要跟踪第一次调用 val_in_array_r 的初始调用帧,然后跳回去到那个rbp,然后替换rsp并做ret

【问题讨论】:

  • 简短回答是否定的。每个递归调用 winds-in 另一个级别的递归。当您return 时,您将返回到前一个递归调用的调用函数。 (从递归函数返回展开)。我不会建议 goto 跳出多个递归调用,但可以想象,只要它符合“近跳”的条件,它应该是可能的。
  • @carl.hiass 尾递归在可检测的情况下已被大多数现代编译器优化。它甚至可能根本不会递归。
  • @DavidSchwartz 我更新了问题,说这不是第一个,抱歉最初的错误。回复:优化/尾递归,是的,假设它没有被编译器优化——我只是从理论/学术的角度好奇。
  • @DavidC.Rankin 什么是“近跳”?想展示一个如何实现的示例?
  • “近跳转”是一个旧术语,它将目标地址限制在同一内存段内。今天,同一段内的跳转不是技术限制(比现在具有 32/64 位允许访问千兆字节内存的整个段要少得多),但接近跳转术语意味着跳转到附近的地址。查看How does the GNU toolchain decide to use near vs. short jump instructions?中的cmets和链接

标签: c


【解决方案1】:

我建议稍微修改一下代码以使事情变得绝对清晰:

if (!size)
    return false;
else if (val == *array)
    return true;
return val_in_array_r(val, array+1, size-1);

现在,很明显这是尾递归。现代编译器对其进行优化应该没有困难,很可能完全删除递归并只是循环回到函数的开头。

编译器应该可以毫无问题地将其优化为:

while (1)
{
    if (!size)
        return false;
    else if (val == *array)
        return true;
    ++array;
    --size;
}

我怀疑一个好的现代编译器即使在原始代码中也能识别尾递归并进行相同的优化。但确定一点永远不会有什么坏处。就个人而言,我几乎总是更喜欢将递归算法转换为迭代算法,以确保可预测性、避免堆栈溢出并简化代码维护。

【讨论】:

  • 如果我能给他们的偏好和建议值得 2 票。
猜你喜欢
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 2015-01-05
  • 2022-11-17
  • 2012-10-24
  • 2016-05-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多