是否在C中的堆栈或堆上创建激活记录?

时间:2016-11-01 18:20:14

标签: c memory stack heap dynamic-memory-allocation

我正在阅读有关内存分配和激活记录的内容。我有些疑惑。任何人都能明白以下吗?

A)。我的第一个疑问是"是否在C" 中的堆栈或堆上创建了激活记录?

B)。这些是我所指的摘要中的几行: - >

  

即使堆栈区域的内存是在运行时创建的 -   内存量(激活记录大小)在编译时确定   时间。静态和全局内存区域是确定的编译时间   这是二进制文件的一部分。在运行时,我们无法改变这一点。只要   内存区域可供进程在运行时更改   是堆。编译时编译器只保留堆栈空间   激活记录。这仅用于(在实际内存中分配)   在程序运行期间。只有DATA段的程序部分像静态一样   变量,字符串文字等在编译期间分配。对于   堆区域,在运行时也确定要分配的内存量   时间。

任何人都可以详细说明这些内容,因为我无法理解任何内容吗? 我相信对我来说非常需要解释。

3 个答案:

答案 0 :(得分:2)

理论上,C99(或C11)兼容的实现(例如,C编译器和C标准库实现)甚至不需要(在所有情况下)call stack。例如,可以想象一个完整的程序编译器(特别是对于独立的C实现),它将分析整个程序并确定不需要堆栈帧(例如,每个局部变量可以静态分配,或者适合寄存器)。或者可以想象一个实现将调用帧分配为continuation帧(可能在编译器进行CPS转换之后)其他地方(例如在某些“堆”中),使用与Appel旧书中描述的技术类似的技术Compiling with Continuations(描述SML / NJ编译器)。

(请记住,编程语言是规范 - 不是某些软件 - 通常用英语编写,可能还有一些技术报告或标准文档中的附加形式化.AFAIK,C99或者C11标准甚至没有提到任何堆栈或激活记录。但是在实践中,大多数C实现都是由编译器和标准库实现组成的。)

实际上,分配记录是调用框架(对于C,它们是同义词;事情对于嵌套函数更复杂)并且在我知道的所有合理C实现上的硬件辅助调用堆栈上分配。在Z/Architecture上没有硬件堆栈指针寄存器,因此它是约定(专用某些寄存器来扮演堆栈指针的角色)。

所以先查看call stack wikipage 。它有一个很好的图片漂亮的图片

  

是否在堆栈或堆上创建激活记录

练习中,他们(激活记录)是call stack上的呼叫帧(在calling conventionsABI之后分配)。当然,编译器在编译时计算调用帧的布局,插槽使用和大小。

practice 中,局部变量可能对应于调用帧内的某个槽。但有时,编译器会将其仅保留在寄存器中,或者重用相同的时隙(在调用帧中具有固定的偏移量)用于各种用途,例如:对于不同块中的几个局部变量等。

但大多数C编译器都是optimizing compilers。他们能够inline一个函数,或者有时对它做tail call(然后调用者的调用框被重用为被调用者调用框或被覆盖的调用框),所以详细信息更复杂。

另请参阅关于复古的这个How was C ported to architectures that had no hardware stack?问题。

答案 1 :(得分:2)

在C中(鉴于它几乎普遍实现*)激活记录堆栈帧完全相同,它与相同>调用框架。它们总是在堆栈上创建。

堆栈段是进程获得的内存区域"免费"从OS创建时。它不需要freeRSP。在x86上,机器寄存器(例如int my_func() { int x = 123; int y = 234; int z = 345; ... return 1; } )指向段的末尾,堆栈帧/激活记录/调用帧被分配"通过递减该寄存器中的指针来分配多少字节。 E.g:

my_func:
    ; "allocate" 24 bytes of stack space
    sub  rsp, 24     
    ; Initialize the allocated stack memory
    mov  [rsp], 345      ; z = 345
    mov  [rsp+8], 234    ; y = 234
    mov  [rsp+16], 134   ; x = 123
    ...
    ; "free" the allocated stack space
    add  rsp, 24
    ; return 1
    mov  rax, 1
    ret

一个未优化的C编译器可以生成汇编代码,用于将这三个变量保存在堆栈帧中,如下所示:

File->New Project->Cross-Platform->Blank App (Xamarin.Forms Portable) 
  • 在其他上下文和语言中,激活记录可以以不同方式实现。例如,使用链接列表。但由于语言是C而且语境是低级编程,所以我认为讨论它是有用的。

答案 2 :(得分:1)

作为一个快速回答,我甚至不知道激活记录是什么。报价的其余部分英语很差,很容易产生误导。

老实说,抽象是在谈论绝对,而在现实中,确实根本没有绝对。你确实在编译时定义了一个主堆栈,是的(虽然你也可以在运行时创建很多堆栈)。

是的,当您想要分配内存时,通常会创建一个指针来存储该信息,但您放置的位置完全取决于您。它可以是堆栈,它可以是全局内存,也可以是来自另一个分配的堆,或者你可以泄漏内存而不是将它存储在任何地方,如果你愿意的话。也许这就是激活记录的含义?

或许,这意味着当在内存中的某个地方创建动态内存时,必须有某种信息来跟踪已使用和未使用的内存。对于许多分配器,这是存储在已分配内存中某处的指针列表,但其他分配器将其存储在不同的内存中,有些甚至可以将其放在堆栈中。这一切都取决于内存系统的需求。

最后,从中分配动态内存的情况也会有所不同。它可以来自对OS的调用,但在某些情况下,它也可以叠加到现有的全局(甚至堆栈)内存中 - 这在嵌入式编程中并不罕见。

正如您所看到的,这个摘要甚至不像动态内存所代表的那样。

其他信息:

许多人都在跳过我,说'C'在标准中没有堆叠。正确。也就是说,有多少人真正用C编码而没有一个?我暂时不管它。

定义的内存,就像你所说的那样,是在函数内声明的'static'关键字或在函数外声明的任何变量,在它前面没有'extern'关键字。这是编译器知道的内存,可以在没有任何其他帮助的情况下保留空间。

分配的内存 - 不是一个好词,因为定义的内存也可以被认为是已分配的。相反,使用术语动态内存。这是您在运行时从堆分配的内存。一个例子:

char *foo;
int my_value;

int main(void)
{
    foo = malloc(10 * sizeof(char));
    // Do stuff with foo
    free(foo);
    return 0;
}
正如您所说的那样,

foo被“定义”为指针。如果没有其他任何东西,它只会保留那么多内存,但是当在main()中到达malloc时,它现在也指向至少10个字节的动态内存。一旦达到空闲状态,该内存现在可供程序用于其他用途。它的分配大小是“动态的”。将其与my_value进行比较,int总是context的大小,而不是其他任何内容。