【问题标题】:Why is there a stack overflow为什么会出现堆栈溢出
【发布时间】:2016-12-05 19:43:14
【问题描述】:

Linux下启动

ulimit -s 1024

限制堆栈大小。

首先是一个可以运行的程序:

#include <stdio.h>

static int calc(int c,int *array)
{
    if (array!=NULL) {
        if (c<=0) {
            array[0]=1;
            return 1;
        }
        calc(c-1,array);
        array[c]=array[c-1]+3;
        return array[c];
    } else {
        int a[2500+c];
        calc(c-1,a);
        a[c]=a[c-1]+3;
        return a[c];
    }
}

int main()
{
    int result;
    result = calc(1000,NULL);
    printf("result = %d\n",result);
}

现在,如果我将 int a[2500+c]; 更改为 int a[2500];,那么程序会因堆栈溢出(分段错误)而崩溃。

我试过这个

  • gcc-4.4.7 -O0, gcc-4.4.7 -O2, gcc-4.4.7 -O3
  • gcc-4.8.4 -O0, gcc-4.8.4 -O2, gcc-4.8.4 -O3
  • gcc-4.9.3 -O0, gcc-4.9.3 -O2, gcc-4.9.3 -O3

如果我使用

ulimit -s 1024

然后带有int a[2500]; 的版本崩溃,而带有int a[2500+c]; 的版本可以工作。

为什么使用可变长度数组 (int a[2500+c];) 的程序版本比使用固定长度数组 (int a[2500];) 的程序版本占用更少的堆栈空间?

【问题讨论】:

  • 注意:如果您(例如)在 main 中使用 calc(20,NULL);,该程序也适用于这两种情况。第二个版本(使用固定长度数组)只使用了 lots 更多的堆栈。
  • 两个版本对我来说都没有错误。
  • 约翰:哪个编译器?
  • 我可以用 gcc 4.8.4 -O0 重现这个。在-O3 上,两个版本都可以正常工作。原因是编译器在函数序言中无条件分配int a[2500];-O0,而不检查array != NULL
  • 也许如果你说 a[2500] 编译器会尝试将数组放入堆栈,但如果你说 a[2500+c] 它会使用堆,因为 a 的大小在编译时间。

标签: c stack local-storage


【解决方案1】:

正如your previous question 的cmets 中提到的,“为什么”是编译器的实现细节。该标准没有规定堆栈的布局方式(或者是否有堆栈)。正如您所见,同一个编译器的不同版本,或者具有不同优化设置的同一个版本,可以改变编译器在后台的工作方式。

话虽如此,编译器可能会为函数中任何地方声明的所有变量分配堆栈空间,这些变量在进入函数时不是 VLA。它可能以某种对开发人员不透明的方式更有效。使用 VLA,直到运行时才知道大小,因此分配方式不同。

在这种情况下,您最好使用 malloc 而不是使用本地数组。这会给你更多的确定性行为。

....
} else {
    int *a = malloc(c * sizeof(int));
    if (a == NULL) {
        perror("malloc failed");
        exit(1);
    }
    calc(c-1,a);
    a[c]=a[c-1]+3;
    int rval = a[c];
    free(a);
    return rval;
}

【讨论】:

  • 您的答案基本上可以归结为:编译器可能会为程序中某处提到的任何变量分配空间(是否为堆栈无关紧要),即使所讨论的变量超出范围。这怎么回答?您基本上是说,无论编译器可以为超出范围的变量分配多少空间,都无法保证永远不会运行的代码......
  • @IngoBlackman 正是如此。没有保证,至少不在给定函数的范围内。
  • @4386427:如果我写类似int *p; { int a[10]; p=&amp;(a[0]); } *p=5; 的东西,这是未定义的行为,因为当我通过*p 访问它时,a 超出了范围。不再允许访问a,因为在我通过*p 访问它时它不是“有效的”。那么,如果标准告诉我访问a 是未定义的行为,为什么还要为a 分配空间?
  • @dbush:所以如果我写int myfunc() { a[1000000000]; } 并且我从不调用myfunc(),那么编译器可能仍会为a 分配空间,因为正如你所说的“没有保证”。这并不完全令人信服。
  • @4386426 因为我的问题中的程序崩溃了;如果没有为a 分配空间,它不会崩溃。
猜你喜欢
  • 1970-01-01
  • 2021-06-18
  • 2012-12-19
  • 1970-01-01
  • 2012-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多