在C语言中,大括号是否充当堆栈框架?

在C语言中,大括号是否充当堆栈框架?,c,memory,stack,C,Memory,Stack,如果我在一组新的大括号内创建一个变量,该变量是从右大括号的堆栈中弹出的,还是一直挂到函数结束?例如: void foo() { int c[100]; { int d[200]; } //code that takes a while return; } d是否会在需要一段时间的代码部分占用内存?否,大括号不充当堆栈框架。在C语言中,大括号只表示一个命名范围,但当控件从堆栈中传递出去时,不会破坏任何内容,也不会弹出任何内容 作为一名编写代码的程序员,

如果我在一组新的大括号内创建一个变量,该变量是从右大括号的堆栈中弹出的,还是一直挂到函数结束?例如:

void foo() {
   int c[100];
   {
       int d[200];
   }
   //code that takes a while
   return;
}

d
是否会在需要一段时间的
代码部分占用内存?

否,大括号不充当堆栈框架。在C语言中,大括号只表示一个命名范围,但当控件从堆栈中传递出去时,不会破坏任何内容,也不会弹出任何内容

作为一名编写代码的程序员,您通常可以将其视为堆栈框架。大括号内声明的标识符只能在大括号内访问,因此从程序员的角度来看,就好像它们在声明时被推到堆栈上,然后在退出作用域时弹出。然而,编译器不必生成在进入/退出时推送/弹出任何内容的代码(通常情况下,它们不需要)

还要注意的是,局部变量可能根本不使用任何堆栈空间:它们可以保存在CPU寄存器或其他一些辅助存储位置,也可以完全优化


因此,理论上,
d
数组可以为整个函数消耗内存。但是,编译器可能会对其进行优化,或者与使用寿命不重叠的其他局部变量共享内存。

我认为它确实超出了范围,但在函数返回之前不会弹出堆栈。因此,在函数完成之前,它仍将占用堆栈上的内存,但在第一个右大括号的下游无法访问。

它取决于实现。我编写了一个简短的程序来测试GCC4.3.4的功能,它在函数开始时一次分配所有堆栈空间。您可以使用-S标志检查gcc生成的程序集。

否,在例程的其余部分,d[]将不在堆栈上。但是alloca()是不同的

编辑:克里斯托弗·约翰逊(以及西蒙和丹尼尔)是对的,我最初的反应是错了。对于CYGWIN上的gcc 4.3.4.1,代码:

void foo(int[]);
void bar(void);
void foobar(int); 

void foobar(int flag) {
    if (flag) {
        int big[100000000];
        foo(big);
    }
    bar();
}
给出:

_foobar:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $400000008, %eax
    call    __alloca
    cmpl    $0, 8(%ebp)
    je      L2
    leal    -400000000(%ebp), %eax
    movl    %eax, (%esp)
    call    _foo
L2:
    call    _bar
    leave
    ret
活到老学到老!快速测试似乎表明AndreyT在多次分配方面也是正确的

添加得太晚了:上面的测试表明该方法不太正确。多年来,它一直在说(重点补充):

“当数组名的作用域结束时,变长数组的空间即被释放。”


你的问题不够清楚,无法明确回答

一方面,编译器通常不会对嵌套块作用域执行任何本地内存分配解除分配。本地内存通常只在函数进入时分配一次,在函数退出时释放

另一方面,当本地对象的生存期结束时,该对象占用的内存可以在以后重新用于另一个本地对象。例如,在这段代码中

void foo()
{
  {
    int d[100];
  }
  {
    double e[20];
  }
}
两个数组通常占用相同的内存区域,这意味着函数
foo
所需的本地存储总量是两个数组中最大的一个所需的,而不是同时对两个数组所需的


后者是否符合
d
继续占用内存直到问题上下文中的函数结束由您决定。

您的变量
d
通常不会从堆栈中弹出。大括号不表示堆栈框架。否则,您将无法执行以下操作:

char var = getch();
    {
        char next_var = var + 1;
        use_variable(next_char);
    }
如果大括号导致真正的堆栈推送/弹出(就像函数调用一样),那么上面的代码将不会编译,因为大括号内的代码将无法访问大括号外的变量
var
(就像子函数无法直接访问调用函数中的变量一样)。我们知道情况并非如此

大括号只是用于范围界定。编译器将把从括号外对“内部”变量的任何访问视为无效,并可能将该内存重新用于其他用途(这取决于实现)。但是,在封闭函数返回之前,它可能不会从堆栈中弹出

更新:以下是。关于具有自动存储持续时间的对象(第6.4.2节):

对于没有可变长度数组类型的对象,其 生存期从与之关联的块的入口开始延长 直到该块的执行以任何方式结束

同一节将术语“寿命”定义为(重点):

对象的生存期是程序执行过程中的一部分 保证为其保留的存储。存在一个对象, 具有恒定地址,并始终保留其最后存储的值 它的一生。如果对象在其生存期之外被引用,则 行为是未定义的

当然,这里的关键词是“保证”。一旦离开内部大括号集的范围,数组的生存期就结束了。存储可能会分配给它,也可能不会分配给它(编译器可能会将空间重新用于其他用途),但任何访问数组的尝试都会调用未定义的行为,并导致不可预知的结果

C规范没有堆栈框架的概念。它只说明结果程序将如何运行,并将实现细节留给编译器(毕竟,在无堆栈CPU上的实现与在有硬件堆栈的CPU上的实现会有很大的不同)。C规范中没有规定堆栈帧将在哪里结束或不在哪里结束。唯一真正知道的方法是在特定的编译器/平台上编译代码并检查生成的程序集。编译器当前的一组优化选项也可能在这方面发挥作用

如果要确保阵列
d
不再是ea
void foo() {
   int c[100];
   int *p;

   {
       int d[200];
       p = d;
   }

   /* Can I access p[0] here? */

   return;
}
#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
        printf("%p\n", (void*) x);
    }
    {
        int b;
        y = &b;
        printf("%p\n", (void*) y);
    }
}
#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
    }
    {
        int b;
        y = &b;
    }
    printf("%p\n", (void*) x);
    printf("%p\n", (void*) y);
}