我正在阅读有关内存分配和激活记录的内容。我有些疑惑。任何人都能明白以下吗?
A)。我的第一个疑问是"是否在C" 中的堆栈或堆上创建了激活记录?
B)。这些是我所指的摘要中的几行: - >
即使堆栈区域的内存是在运行时创建的 - 内存量(激活记录大小)在编译时确定 时间。静态和全局内存区域是确定的编译时间 这是二进制文件的一部分。在运行时,我们无法改变这一点。只要 内存区域可供进程在运行时更改 是堆。编译时编译器只保留堆栈空间 激活记录。这仅用于(在实际内存中分配) 在程序运行期间。只有DATA段的程序部分像静态一样 变量,字符串文字等在编译期间分配。对于 堆区域,在运行时也确定要分配的内存量 时间。
任何人都可以详细说明这些内容,因为我无法理解任何内容吗? 我相信对我来说非常需要解释。
答案 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 conventions和ABI之后分配)。当然,编译器在编译时计算调用帧的布局,插槽使用和大小。
在 practice 中,局部变量可能对应于调用帧内的某个槽。但有时,编译器会将其仅保留在寄存器中,或者重用相同的时隙(在调用帧中具有固定的偏移量)用于各种用途,例如:对于不同块中的几个局部变量等。
但大多数C编译器都是optimizing compilers。他们能够inline一个函数,或者有时对它做tail call(然后调用者的调用框被重用为被调用者调用框或被覆盖的调用框),所以详细信息更复杂。
另请参阅关于复古的这个How was C ported to architectures that had no hardware stack?问题。
答案 1 :(得分:2)
在C中(鉴于它几乎普遍实现*)激活记录与堆栈帧完全相同,它与相同>调用框架。它们总是在堆栈上创建。
堆栈段是进程获得的内存区域"免费"从OS创建时。它不需要free
或RSP
。在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)
答案 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
的大小,而不是其他任何内容。