什么时候分配堆栈

Ing*_*man 0 c c++ stack

即使在C(不仅仅是C++)中,您也可以在代码块的开头声明变量,该代码块用大括号括起来.

例:

#include <stdio.h>
void use_stack(int cnt)
{
    if (cnt<=16) {
        int a[16];
        int i;
        a[0]=3;
        a[1]=5;
        for (i=2;i<cnt;i++) {
            a[i]=a[i-1]+a[i-2];
        }
        printf("a[%d] == %d\n",cnt-1,a[cnt-1]);
    }
    else {
        printf("cnt is too big\n");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我知道a[16]在这种情况下,像数组这样的变量会在堆栈上分配.

我想知道这个数组的空间是在函数的开头分配的(第一个打开大括号)还是在声明它的块的开头(打开大括号后if).

从检查汇编程序代码开始,编译器似乎a[16]直接在函数入口处分配空间.

我实际上期望在声明处分配堆栈(堆栈指针减少),a[16]并且在相应if代码块的末尾将堆栈解除分配(堆栈指针增加).

但是这似乎没有发生(堆栈a[16]直接在函数入口处分配,即使a[16]else分支中没有使用).

有没有人解释为什么会这样?

那么是否有C语言标准的任何部分,它解释了这种行为,还是与"longjmp"或信号处理有关,这可能要求堆栈指针在函数内部是"常量"?

注意:我假设堆栈的原因是在代码块的开头/结尾分配/解除分配,因为在C++中,在堆栈上分配的对象的构造函数/析构函数将在代码块的开头/结尾处调用.因此,如果您检查C++程序的汇编代码,您会注意到堆栈仍然在函数入口处分配; 只是构造函数/析构函数调用将在代码块的开始/结束时完成.

我明确感兴趣为什么堆栈没有使用花括号在代码块的开头/结尾分配/解除分配.

问题:在什么时刻是本地变量分配存储?仅涉及在函数开始时分配的局部变量.我很惊讶稍后在代码块内分配的变量的堆栈分配也在函数入口处完成.

到目前为止,答案是:

  • 与优化有关
  • 可能与C,C++不同
  • 在C语言规范中甚至没有提到堆栈

所以:我对C的答案感兴趣...(我坚信答案也适用于C++,但我不是在问C++ :-)).

优化:这是一个例子,它将直接说明为什么我如此惊讶,以及为什么我很确定这不是一个优化:

#include <stdio.h>

static char *stackA;
static char *stackB;

static void calc(int c,int *array)
{
    int result;
    if (c<=0) {
        // base case c<=0:
        stackB=(char *)(&result);
        printf("stack ptr calc() = %p\n",stackB);
        if (array==NULL) {
            printf("a[0] == 1\n");
        } else {
            array[0]=1;
        }
        return;
    }

    // here: c>0
    if (array==NULL) {
        // no array allocated, so allocate it now
        int i;
        int a[2500];

        // calculate array entries recursively
        calc(c-1,a);

        // calculate current array entry a[c]
        a[c]=a[c-1]+3;

        // print full array
        for(i=0;i<=c;i++) {
            printf("a[%d] = %d\n",i,a[i]);
        }
    } else {
        // array already allocated
        calc(c-1,array);

        // calculate current array entry a[c]
        array[c]=array[c-1]+3;
    }
}

int main()
{
    int a;
    stackA=(char *)(&a);
    printf("stack ptr main() = %p\n",stackA);
    calc(9,NULL);
    printf("used stack = %d\n",(int)(stackA-stackB));
}
Run Code Online (Sandbox Code Playgroud)

我知道这是一个丑陋的程序:-).

该函数以递归方式calc计算n*3 + 1所有0<=n<=c.

如果您查看代码,calc请注意该数组a[2500]array在函数的输入参数时声明NULL.

现在这只发生在完成的调用calcmain.

stackAstackB指针被用来计算一个粗略的估计多栈是如何使用该程序.

现在:int a[2500]应该消耗大约10000字节(每个整数4个字节,2500个条目).所以你可以期望整个程序消耗大约10000字节的堆栈+额外的东西(用于calc递归调用时的开销).

但是:事实证明,这个程序消耗大约100000字节的堆栈(是预期的10倍).原因是,对于每个calc数组调用都会a[2500]被分配,即使它仅在第一次调用中使用.有10次调用calc(0<=c<=9),所以你消耗100000字节的堆栈.

  • 它并没有,如果你有或没有优化的编译程序没关系
  • GCC-4.8.4和clang for x64,MS Visual C++ 2010,Windriver for DIAB(for PowerPC)表现出这种行为

甚至更奇怪:C99引入了可变长度阵列.如果我用int a[2500];上面的代码替换,int a[2500+c];那么程序使用更少的堆栈空间(减少大约90000字节).

注意:如果我更改调用calcin maincalc(1000,NULL);程序会崩溃(堆栈溢出==分段错误).如果我另外更改int a[2500+c];程序工作并使用少于100KB的堆栈.我仍然希望看到一个答案,这解释了为什么一个可变长度数组并没有导致堆栈溢出,而一固定长度的数组确实导致堆栈溢出,特别是因为该固定长度阵列超出范围(除第一次调用calc).

那么在C中出现这种行为的原因是什么?

我不相信GCC/clang都不能做得更好; 我坚信必须有技术上的理由.有任何想法吗 ?

由Google回答

经过更多的谷歌搜索:我坚信这与"setjmp/longjmp"行为有关.谷歌为"可变长度阵列longjmp",并亲自看看.如果你没有在函数入口分配所有数组,似乎很难实现longjmp .

Che*_*Alf 5

自动存储的语言规则仅保证最后分配的是第一个被释放的.

编译器可以以它认为合适的方式实现此逻辑堆栈.

如果它可以证明函数不是递归的,它甚至可以在程序启动时分配存储.


我相信上面的内容适用于C和C++,但我不是C专家.

当您询问编程语言的详细信息时,请一次将问题限制为一种语言.