堆栈分配的确切时间 [英] When exactly is stack allocated

查看:30
本文介绍了堆栈分配的确切时间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Even in C (not just C++) you can declare variables at the start of a code block, which is enclosed in curly braces.

示例:

#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");
    }
}

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

Now I know that variables like the array a[16] are allocated on the stack in this case.

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

I was wondering if the space for this array is allocated at the start of the function (first opening curly brace) or at the start of the block where it is declared (opening curly brace after if).

从检查汇编代码来看,编译器似乎直接在函数入口处为 a[16] 分配了空间.

From examining the assembler code it seems the compiler allocates the space for a[16] directly at the entry of the function.

我实际上希望在 a[16] 声明时分配堆栈(堆栈指针减少),并且在结束时取消分配堆栈(堆栈指针增加)对应的 if 代码块.

I actually expected that the stack would be allocated (stack pointer decreased) at the declaration of a[16] and that the stack would be de-allocated (stack pointer increased) at the end of the corresponding if code block.

但这似乎不会发生(a[16] 的堆栈直接在函数入口处分配,即使 a[16] 未在 a[16]code>else 分支).

But this does not happen it seems (stack for a[16] is allocated directly at function entry, even if a[16] is not used in the else branch).

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

Has anyone an explanation why this is the case ?

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

So is there any part of the C language standard, which explains this behavior, or does it have to do with things like "longjmp" or signal handling, which maybe require that the stack pointer is "constant" inside a function ?

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

Note: The reason why I assumed the stack would be allocated/deallocated at the start/end of the code block is, because in C++ the constructor/destructor of objects allocated on the stack will be called at the start/end of the code block. So if you examine the assembler code of a C++ program you will notice that the stack is still allocated at the function entry; just the constructor/destructor call will be done at the start/end of the code block.

我对为什么不在代码块的开始/结束使用大括号分配/解除分配堆栈很感兴趣.

I am explicitly interested why stack is not allocated/deallocated at the start/end of a code block using curly braces.

问题:局部变量在什么时候分配存储空间?只是关于局部变量在一个函数的开始.我很惊讶代码块中稍后分配的变量的堆栈分配也在函数入口处完成.

The question: At what exact moment is a local variable allocated storage? is only about a local variable allocated at the start of a function. I am surprised that stack allocation for variables allocated later inside a code block is also done at the function entry.

到目前为止,答案是:

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

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

So: I am interested in the answer for C... (and I strongly believe that the answer will apply to C++ also, but I am not asking about C++ :-)).

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

Optimization: Here is an example which will directly demonstrate why I am so surprised and why I am quite sure that this is not an optimization:

#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));
}

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

I am aware that this is an ugly program :-).

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

The function calc calculates n*3 + 1 for all 0<=n<=c in a recursive fashion.

如果您查看 calc 的代码,您会注意到数组 a[2500] 仅在输入参数 array 为函数为NULL.

If you look at the code for calc you notice that the array a[2500] is only declared when the input parameter array to the function is NULL.

现在这只发生在对 calc 的调用中,该调用在 main 中完成.

Now this only happens in the call to calc which is done in main.

stackAstackB 指针用于粗略估计该程序使用了多少堆栈.

The stackA and stackB pointers are used to calculate a rough estimate how much stack is used by this program.

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

Now: int a[2500] should consume around 10000 bytes (4 bytes per integer, 2500 entries). So you could expect that the whole program consumes around 10000 bytes of stack + something additional (for overhead when calc is called recursively).

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

But: It turns out this program consumes around 100000 bytes of stack (10 times as much as expected). The reason is, that for each call of calc the array a[2500] is allocated, even if it is only used in the first call. There are 10 calls to calc (0<=c<=9) and so you consume 100000 bytes of stack.

  • 编译程序是否优化都无关
  • 用于 x64 的 GCC-4.8.4 和 clang、MS Visual C++ 2010、用于 DIAB 的 Windriver(用于 PowerPC)所有都表现出这种行为
  • It does not matter if you compile the program with or without optimization
  • GCC-4.8.4 and clang for x64, MS Visual C++ 2010, Windriver for DIAB (for PowerPC) all exhibit this behavior

更奇怪的是:C99 引入了可变长度数组.如果我将上面代码中的 int a[2500]; 替换为 int a[2500+c]; 那么程序将使用 less 堆栈空间(大约少 90000 字节).

Even weirder: C99 introduces Variable Length Arrays. If I replace int a[2500]; in the above code with int a[2500+c]; then the program uses less stack space (around 90000 bytes less).

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

Note: If I only change the call to calc in main to calc(1000,NULL); the program will crash (stack overflow == segmentation fault). If I additionally change to int a[2500+c]; the program works and uses less than 100KB stack. I still would like to see an answer, which explains why a Variable Length Array does not lead to a stack overflow whereas a fixed length array does lead to a stack overflow, in particular since this fixed length array is out of scope (except for the first invocation of calc).

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

So what's the reason for this behavior in C ?

我不相信 GCC/clang 两者都不能做得更好;我坚信这一定有技术原因.有什么想法吗?

I do not believe that GCC/clang both simply are not able to do better; I strongly believe there has to be a technical reason for this. Any ideas ?

谷歌回答

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

After more googling: I strongly believe this has something to do with "setjmp/longjmp" behavior. Google for "Variable Length Array longjmp" and see for yourself. It seems longjmp is hard to implement if you do not allocate all arrays at function entry.

推荐答案

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

The language rules for automatic storage only guarantees that the last allocated is the first deallocated.

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

A compiler can implement this logical stack any way it sees fit.

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

If it can prove that a function isn't recursive it can even allocated the storage at program start-up.

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

I believe that the above applies to C as well as C++, but I'm no C expert.

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

Please, when you ask about the details of a programming language, limit the question to one language at a time.

这篇关于堆栈分配的确切时间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆