你的问题问得很好,但是要做好应对一些额外复杂性的准备,因为其中一些东西会接触到运行代码的 CPU 中的裸露晶体管......
我选择将其压缩为 4 种类型,但惨遭失败。
代码存储应该非常简单,所以我只是将其复杂化一点:将一系列思想转化为过程语言片段(此后称为“函数”),然后将其转化为一系列机器指令。
这些需要由处理器获取并执行,因此它必须位于 ROM 或 RAM 中......定义代码存储区域。即使该函数被多次调用甚至递归调用,其代码也只有一个实例。
(我们不要进入内联)
全局数据存储是 RAM,所有不在函数中声明的变量都可以在其中生存(每个声明只有一个实例),加上声明为静态的变量(在函数内或函数外)。这些由绝对(或准)地址引用。
常量可能有也可能没有自己的 ROM 存储区域……其编译器/优化特定。
函数的本地存储是用于参数/返回传递和局部变量的包(在其中声明,因此仅对其可见),并且对函数的每次调用都需要一个单独的包,以便每次调用都获得其单独的上下文。
通常(也有令人难忘的例外)我们使用Call-Stack机制来堆积每个函数调用生成的几个本地存储区域。它越来越不确定,但你会发现这是一个以递减内存地址增长的堆栈帧。
因此,本地存储是编译器函数调用代码(自动为您完成)放置函数参数、被调用函数的返回占位符、调用函数的返回地址以及所有非静态(并优化为内部寄存器使用)变量。每个呼叫一个。引用这些局部变量和参数的函数代码将...错误..通过堆栈指针加上偏移量(指向当前堆栈帧结束地址的CPU寄存器)来引用后者(函数代码怎么可能否则知道内存地址吗?)。
调用函数涉及设置其本地存储,最终将参数复制到其中(推入堆栈)并初始化一些本地变量,然后跳转到函数代码。返回是将被调用函数的返回值复制到调用函数的本地存储中,销毁被调用的F本地存储,将堆栈指针放在之前堆积的本地存储袋的末尾,并跳转到保存的调用者下一条指令地址(弹出东西)。全做完了。
查看单个 C 进程的内存快照,您可以看到一个具有多个底部堆积的本地存储区域的堆栈,这些区域将按照特定程序执行的函数调用/返回模式阻止增长和收缩。
动态内存通常由 malloc 和 free 管理:它们是通过指针访问的不断增长的地址堆栈的内存块的守护者。这形成了一个错误命名的堆区域(因为随着时间的推移,您很可能会得到瑞士奶酪,而不是堆)。
这些内存块可用于任何目的,挑战是确保不要忘记释放它们,否则及时您将“泄漏”到调用堆栈中(很容易看出这将是多么具有破坏性)。
使用由 malloc/free 管理的动态内存需要一些额外的规则,但对于避免巨大的本地或浪费的全局存储非常有用(在函数需要处理一堆数据并且可以被调用几次的情况下,或者很少需要存储在内存中的一大块数据)。
动态内存区域通常夹在全局和堆栈之间...一些嵌入式编译器将允许您指定两者的大小。
Malloc 和 free 并不是管理动态内存的唯一方法,我目前正在使用一个自制的反向引用分配器,它不需要 free()。无纪律的代码,没有泄漏......耶!