预先在运行时检测堆栈溢出

时间:2016-02-05 11:32:09

标签: c recursion stack overflow detection

我有一个相当大的递归函数(我也用C编写),虽然我毫不怀疑堆栈溢出发生的情况极不可能,但它仍然是可能的。我想知道你是否可以检测堆栈是否会在几次迭代中溢出,这样你就可以在不崩溃程序的情况下进行紧急停止。

4 个答案:

答案 0 :(得分:9)

在C编程语言本身,这是不可能的。一般来说,在用完之前,你很难知道你的堆栈用完了。我建议您在实现中对递归深度设置一个可配置的硬限制,这样您就可以在超出深度时简单地中止。您还可以重写算法以使用辅助数据结构而不是通过递归使用堆栈,这为您提供了更大的灵活性来检测内存不足的情况; malloc()会告诉您何时失败。

但是,您可以在类UNIX系统上使用类似的过程获得类似的东西:

  1. 使用setrlimit将软堆栈限制设置为低于硬堆栈限制
  2. SIGSEGVSIGBUS建立信号处理程序,以获得堆栈溢出的通知。某些操作系统为这些操作系统生成SIGSEGV,其他操作系统生成SIGBUS
  3. 如果您收到这样的信号并确定它来自堆栈溢出,请使用setrlimit提高软堆栈限制并设置一个全局变量以识别出现这种情况。设置变量volatile,以便优化器不会破坏您的平原。
  4. 在您的代码中,在每个递归步骤中,检查是否已设置此变量。如果是,则中止。
  5. 这可能无处不在,需要特定于平台的代码才能发现信号来自堆栈溢出。并非所有系统(特别是早期的68000系统)在获得SIGSEGVSIGBUS后都可以继续正常处理。

    Bourne shell使用类似的方法进行内存分配。

答案 1 :(得分:4)

这是一个适用于win-32的简单解决方案。实际上类似于Wossname已发布但不那么icky:)

unsigned int get_stack_address( void )
{
    unsigned int r = 0;
    __asm mov dword ptr [r], esp;
    return r;
}
void rec( int x, const unsigned int begin_address )
{
    // here just put 100 000 bytes of memory
    if ( begin_address - get_stack_address() > 100000 )
    {
        //std::cout << "Recursion level " << x << " stack too high" << std::endl;
        return;
    }
    rec( x + 1, begin_address );
}
int main( void )
{
    int x = 0;
    rec(x,get_stack_address());
}

答案 2 :(得分:3)

这是一个天真的方法,但它有点icky ......

第一次输入函数时,可以存储在该函数中声明的一个变量的地址。将该值存储在函数之外(例如,在全局中)。在后续调用中,将该变量的当前地址与缓存副本进行比较。你越深入,这两个值就会越远。

这很可能会导致编译器警告(存储临时变量的地址),但它确实可以让您以相当准确的方式知道您正在使用多少堆栈。

不能说我真的推荐这个但它会起作用。

#include <stdio.h>

char* start = NULL;

void recurse()
{
  char marker = '@';

  if(start == NULL)
    start = &marker;

  printf("depth: %d\n", abs(&marker - start));

  if(abs(&marker - start) < 1000)
    recurse();
  else
    start = NULL;
}

int main()
{
  recurse();

  return 0;
}

答案 3 :(得分:2)

另一种方法是在程序开始时学习堆栈限制,并在每次递归函数中检查是否已接近此限制(在某个安全范围内,比如说64 kb)。如果是这样,中止;如果没有,继续。

可以使用getrlimit系统调用来学习POSIX系统的堆栈限制。

线程安全的示例代码:(注意:它代码假定堆栈向后增长,就像x86一样!)

#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>

void *stack_limit;
#define SAFETY_MARGIN (64 * 1024) // 64 kb

void recurse(int level)
{
    void *stack_top = &stack_top;
    if (stack_top <= stack_limit) {
        printf("stack limit reached at recursion level %d\n", level);
        return;
    }
    recurse(level + 1);
}

int get_max_stack_size(void)
{
   struct rlimit rl;
   int ret = getrlimit(RLIMIT_STACK, &rl);
   if (ret != 0) {
       return 1024 * 1024 * 8; // 8 MB is the default on many platforms
   }
   printf("max stack size: %d\n", (int)rl.rlim_cur);
   return rl.rlim_cur;
}

int main (int argc, char *argv[])
{
    int x;
    stack_limit = (char *)&x - get_max_stack_size() + SAFETY_MARGIN;
    recurse(0);
    return 0;
}

输出:

max stack size: 8388608
stack limit reached at recursion level 174549