【问题标题】:Segmentation fault in a recursive function. Using std::stack (C++)递归函数中的分段错误。使用 std::stack (C++)
【发布时间】:2012-12-20 01:35:48
【问题描述】:

为什么会出现分段错误以及如何解决?

我正在编写下面的代码以递归方式“遍历”迷宫并找到路径总数。我正在使用堆栈来跟踪“下一步”。

ROWS 和 COLUMNS 定义了迷宫的大小。如果我使这些参数大于 9x9,则会出现分段错误。我不知道我为什么会这样。

这是我第一次使用gdb,得到以下结果:

#3  0x0000000000400de0 in std::stack<xy, std::deque<xy, std::allocator<xy> > >::top (this=0x604400 <buff>)
at /usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/bits/stl_stack.h:161
161     return c.back();

让我相信这与我的堆栈有关。

感谢您的帮助。谢谢。

代码:

#define ROWS 10
#define COLUMNS 10

#include<iostream>
#include<stack>
using namespace std;

struct xy
{
  long i;
  long j;
};

stack<xy> buff;

long goRight (long j)
{
  if (j < COLUMNS-1)
    return 1;
  else
    return 0;
}

long goDown (long i)
{
  if (i < ROWS-1)
    return 1;  
  else
    return 0;
}

long traverse (xy POS)
{
  long fD = goDown(POS.i);
  long fR = goRight(POS.j);

  xy toAdd;

  if (fD == 1)
    {
      toAdd.i=POS.i+1;
      toAdd.j=POS.j;
      buff.push(toAdd);
    }

  if (fR == 1)
    {
      toAdd.i=POS.i;
      toAdd.j=POS.j+1;
      buff.push(toAdd);
    }

  if(buff.empty())
    return 0;

  toAdd = buff.top();
  buff.pop();

  return (traverse(toAdd) + (fD * fR));
}

int main()
{
  xy initial;
  initial.i=0;
  initial.j=0;

  cout << 1 + traverse(initial);

  return 1;
}

【问题讨论】:

  • 为什么使用递归而不是迭代?
  • 使用递归没有特别的原因。我首先想到了这个解决方案——仅此而已。

标签: c++ recursion stl stack segmentation-fault


【解决方案1】:

您的call stack 的内存量有限,您调用的每个函数都在使用该堆栈上的内存。因此,通过一个巨大的迷宫递归地进行暴力破解最终会导致 stack overflow。使用 gdb 的 backtrace 功能应该可以清楚地显示出来。

您应该重新考虑您的算法并使用迭代而不是递归。 在您的情况下,您似乎想要实现某种flood fill algorithm,这很容易通过迭代来实现,如link 中所述。

还请注意,如果您使用的是 linux 系统,则使用 ulimit -s unlimited 将允许您为您的 call stack 使用无限量的内存,以便从该终端模拟器运行的所有程序。那么你的程序不应该出现段错误。

祝你好运。

【讨论】:

  • 明白 - 我将切换到迭代。有没有办法绕过这个限制?
  • @lamdacore 即使有(似乎有),您也不想坚持使用递归,因为这会占用大量内存!在这种情况下,迭代要好得多。
  • 我认为不需要切换到迭代,我只是认为你的算法不正确
  • 我没有检查他的洪水填充算法的实现是否正确,但这不是递归实现的算法。请注意,如果使用无限堆栈执行,他的程序不会出现段错误,它会返回 48620。
  • 谢谢。我不得不尝试 ulimit -s unlimited 用于科学,它适用于 10x10。但是在 15x15 时,我的电脑完全死机了。 :) @AndyProwl - 不正确,因为我可以使其高效或做错并进入无限递归?
【解决方案2】:

我相信你不需要 std::stack 对象来实现你想要的(即计算路径的数量)。而且你不一定要切换到迭代,这使得这个算法更难编写,只需简化算法及其终止条件。

这是一个可能的解决方案(顺便说一句,如果您打算增加行数和列数,可以考虑切换到 long long 以避免 整数 - 不是堆栈 - 溢出):

#define ROWS 10
#define COLUMNS 10

#include<iostream>
#include <stack>

using namespace std;

struct xy
{
  long i;
  long j;
};

stack<xy> partialPath;

long goRight (long j)
{
  if (j < COLUMNS - 1)
    return 1;
  else
    return 0;
}

long goDown (long i)
{
  if (i < ROWS - 1)
    return 1;
  else
    return 0;
}

long traverse (xy const& POS)
{
    partialPath.push(POS); // Not needed to count the # of paths, but if you want...

    long fD = goDown(POS.i);
    long fR = goRight(POS.j);

    xy nextPos;
    long numOfPaths = 0;
    if (fD == 1)
    {
        nextPos.i=POS.i+1;
        nextPos.j=POS.j;
        numOfPaths += traverse(nextPos);
    }

    if (fR == 1)
    {
        nextPos.i=POS.i;
        nextPos.j=POS.j+1;
        numOfPaths += traverse(nextPos);
    }

    partialPath.pop(); // Not needed to count the # of paths, but if you want...

    if ((fD == 0) && (fR == 0))
    {
        return 1;
    }

    return numOfPaths;
}

int main()
{
  xy initial;
  initial.i=0;
  initial.j=0;

  cout << traverse(initial);

  return 0;
}

P.S:为了提高效率,最好通过 const ref 传递 POS 参数。另外,我认为用于指示您的进程成功的返回码应该是 0 而不是 1(我的意思是在 main()return 语句中)。

【讨论】:

  • 非常感谢您的解决方案。确实,有一种更好的方法。
  • 无论如何,如果您想跟踪路径而不遇到分段错误,您仍然可以使用此解决方案使用您的std::stack。我将编辑解决方案向您展示
  • @lamdacore:你去吧。这就是说,基于迭代的实现确实会更高效,更具可扩展性,但我认为“否则就这样做”并不是你问题的真正答案,因为递归可以很好地处理你的情况并且是最优雅的算法恕我直言。
  • 确实,我认为递归算法要优雅得多。再次感谢您的帮助!
猜你喜欢
  • 2017-12-25
  • 1970-01-01
  • 1970-01-01
  • 2016-01-14
  • 2019-08-13
  • 2013-03-05
  • 2014-11-05
相关资源
最近更新 更多