3

在 C 中,函数是在什么时间分配的,它在内存中的什么位置?

函数的内存是在第一次编译程序时分配的,还是在第一次看到函数调用时分配的?它是在堆栈上还是在代码段中分配的?

4

3 回答 3

3

你的问题是个好问题,但要为一些额外的复杂性做好准备,因为其中一些东西会触及运行代码的 CPU 中的裸晶体管......

我选择惨败,试图将其浓缩为 4 种类型。

  • 全局存储
  • 代码存储
  • 本地存储
  • 动态存储

代码存储应该非常简单,所以我将其复杂化一点:一组思想被翻译成程序语言片段,此后称为“函数”,被翻译成一系列机器指令。

这些需要由处理器获取并执行,因此它必须位于 ROM 或 RAM 中……定义代码存储区域。即使函数被多次调用甚至递归调用,其代码也只有一个实例。

(让我们不要进入内联)


全局数据存储是 RAM,其中所有非函数声明的变量都可以存活(每个声明只有一个实例),加上声明为静态的变量(在函数内或函数外)。这些被绝对(或准)地址引用。

常量可能会或可能不会获得自己的 ROM 存储区域......它的编译器/优化特定。


函数的本地存储是一个用于参数/返回传递和局部变量的包(在其中声明,因此仅对其可见),并且对函数的每次调用都需要一个单独的包,以便每次调用都获得它的单独上下文。

通常(有一些令人难忘的例外)我们使用调用堆栈机制来堆积每个函数调用生成的几个本地存储区域。它越来越不确定,但你会发现这是一个随着内存地址递减而增长的堆栈帧。

因此,本地存储是编译器函数调用代码(为您自动完成)放置函数参数、被调用函数的返回占位符、调用函数的返回地址以及所有非静态(并优化为内部寄存器使用)变量。每个呼叫一个。引用这些局部变量和参数的函数代码将...err..通过堆栈指针加上偏移量来引用后者,一个指向当前堆栈帧结束地址的CPU寄存器(函数代码怎么可能否则知道内存地址?)。

调用函数涉及设置它的本地存储,最终将参数复制到其中(推入堆栈)并初始化一些本地,然后跳转到函数代码。Returning就是将被调用函数的返回值复制到caller-function的Local存储中,销毁被调用的-F本地存储,将Stack Pointer放在之前堆积的Local存储包的尾部,跳转到保存的caller下一条指令地址(弹出东西)。全部完成。

查看单个 C 进程的内存快照,您可以创建一个堆栈,其中包含多个底部堆积的本地存储区域,这些存储区域将按照特定程序执行的函数调用/返回模式阻止增长和收缩。


动态内存通常由 malloc 和 free 管理:您通过指针访问的内存块的增长地址堆栈的保持者。这形成了一个错误命名的堆区域(因为随着时间的推移,您很可能会得到一块瑞士奶酪,而不是堆)。

这些内存块可用于任何目的,挑战在于确保不会忘记释放它们,否则您将及时“泄漏”到调用堆栈中(很容易看出这将是多么具有破坏性)。

使用由 malloc/free 管理的动态内存需要一些额外的纪律,但对于避免巨大的本地或浪费的全局存储非常有用(在函数需要处理一堆数据并且可以被调用几次的情况下,或很少需要在内存中的大量数据)。

Dyn-memory 区域通常夹在 global 和 Stack 之间……一些嵌入式编译器会让你指定两者的大小。

Malloc 和 free 不是管理动态内存的唯一方法,我目前正在使用不需要 free() 的本土反向引用分配器。无纪律的代码,没有泄漏.... yaayy!

于 2012-07-10T09:32:09.867 回答
2

C 函数的内存总是在函数加载到内存时从代码段分配。如果一个函数属于一个动态链接库,程序可以在任意时间加载和卸载它。

于 2012-07-10T04:28:20.357 回答
0

内联函数...

很好的解释...

我想提供有关未涵盖的内联函数的内存分配的详细信息。内联函数可以被认为是一个宏,其中对函数的调用被代码替换,并且还进行参数评估。在函数之前给出关键字 inline 不需要使其成为内联函数,它只是建议编译器这是内联的候选者。编译器最终选择这是否可以是内联函数。因此,内存分配类似于函数。

于 2014-02-20T11:05:45.867 回答