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

问题描述 投票:1回答:3

我正在阅读有关内存分配和激活记录的内容。我有些疑惑。任何人都可以制作以下水晶吗?

一个)。我的第一个疑问是“在堆栈中创建激活记录还是在C中堆积”?

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

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

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

c memory stack heap dynamic-memory-allocation
3个回答
2
投票

在C中(给出它几乎普遍实现的方式*)激活记录与堆栈帧完全相同,与调用帧相同。它们总是在堆栈上创建。

堆栈段是进程在创建时从操作系统“免费”获得的内存区域。它不需要mallocfree它。在x86上,机器寄存器(例如RSP)指向段的末尾,并且通过将该寄存器中的指针递减多少字节来“分配”堆栈帧/激活记录/调用帧。例如:

int my_func() {
    int x = 123;
    int y = 234;
    int z = 345;
    ...
    return 1;
}

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

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而上下文是低级编程,我认为讨论它并不有用。

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 conventionsABIs之后分配)。当然,编译器在编译时计算调用帧的布局,插槽使用和大小。

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

但大多数C编译器都是optimizing compilers。他们能够使inline成为一个函数,或者有时会对它进行tail call(然后调用者的调用帧被被调用者调用帧重用或覆盖),因此细节更复杂。

另见How was C ported to architectures that had no hardware stack?关于复古的问题。


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总是qazxswpoi的大小,没有别的。

© www.soinside.com 2019 - 2024. All rights reserved.