C 是不是需要堆栈和堆才能运行?

Posted

技术标签:

【中文标题】C 是不是需要堆栈和堆才能运行?【英文标题】:Does C need a stack and a heap in order to run?C 是否需要堆栈和堆才能运行? 【发布时间】:2019-01-05 17:50:01 【问题描述】:

人们谈论堆栈和堆是什么以及它们之间的区别。但是我很想知道,如果一个CPU不支持栈堆结构,那么C没有栈堆也能正常运行吗?

【问题讨论】:

询问“我从哪里开始”的问题通常过于宽泛,不适合本网站。人们有自己解决问题的方法,因此不可能有正确的答案。好好阅读Where to Start,然后写下你的帖子。 Possible duplicate 但这对于一个问题来说太宽泛了。您一次询问多种编程语言的答案。我建议你删除它。 投票重新开放,因为 OP 现在已经编辑了问题 实现堆栈或堆所需的只是随机存取内存,因此即使您的 CPU 不“原生”支持堆栈或堆,您仍然可以将它们作为 C 运行时库的一部分来实现. 【参考方案1】:

C 语言需要(正如 chqrlie 所指出的)某种机制来实现自动存储和跟踪函数调用返回点。在实践中,这几乎总是一个堆栈。但它不需要堆。

只有当你使用像malloc这样的库函数时,通常才需要堆;而不是 C 语言本身。堆实际上并没有什么神奇之处——你可以用纯 C 语言编写 mallocfree。它只是有一大块 static 内存,并且有一个从块中分配空间的算法。

您问“如果 CPU 不支持堆栈和堆结构”怎么办? 好吧,堆栈和堆只是由内存和指针构成的。我不认为你会找到任何可以被视为“CPU”的处理器的例子,它不具备这种能力。

【讨论】:

我不同意:stack 这个词甚至没有出现在 C 标准中。需要一些机制来实现自动存储和跟踪函数调用返回点,但它不一定是 CPU 堆栈。 @chqrlie 这是一个真正有趣的观点。我将编辑我的答案。但我怀疑实际上所有 C 实现都使用堆栈。你知道有哪些不知道吗? 不,我不知道,但是 A C 解释器可以使用双向链接的帧列表。许多处理器没有内置堆栈:例如,许多 RISC 架构没有带有隐式堆栈的call/ret 机制,而是使用可以通过适当生成的代码以各种方式保存的链接寄存器. IBM System Z 使用动态分配来实现自动存储。我知道至少有一个不完全晦涩难懂的系统/C 实现以这种方式工作,但不得不用谷歌搜索它是什么。 (这把我带到了这个问答环节,特别是 Paxdiablo 的回答,而这个回答通过说“总是”来稍微夸大了一些事情。) 有些 8 位微控制器没有全角指针寄存器,如 6502 和 8080 / Z80。正如Why do C to Z80 compilers produce poor code? 中所讨论的,在 C 实现中支持递归/重入是可能的,但非常笨拙。对于那些老旧且需要在狭窄系统上运行的系统,编译器无济于事,限制了他们在不需要完整的一般情况下慢版本时找到窥视孔优化的能力,但有些事情在这些机器上很难。【参考方案2】:

不,它没有。让我们先覆盖堆,这很简单。

不提供任何类型堆的实现只需要在您尝试调用malloc(或任何其他内存分配函数)时返回NULL。根据标准,这是完全可以接受的行为。


就堆栈而言,它也不需要提供一个。 ISO C11 提到“堆栈”这个词的次数恰好为零次。

实现需要做的只是成为标准中指定的所有事物的正确“虚拟机”。诚然,如果没有堆栈,这将非常困难,但这并非不可能。作为一个极端的例子,没有什么说你不能简单地递归地内联每个函数调用。这将使用相当大量的代码和特定于函数的数据空间,但它肯定是可行的。

但是,这可能会说服我转移到另一个架构,确实有一个堆栈(和堆,就此而言)。


话虽如此,即使架构既不提供堆也不提供堆栈,这两者都可以由基本的内存 I/O 操作构建而成。事实上,我十几岁时拥有的最早的计算机之一配备了 RCA 1802 CPU,它没有专用堆栈。它甚至没有callret 指令。

然而,它可以使用它的 SCRT(标准调用和返回技术)很好地处理子例程和堆栈(对于“好”这个词的某些定义)。请参阅 here 了解更多关于这个美丽事物(或怪物,取决于你的观点)如何工作的详细信息,以及其他一些不寻常的架构。


IBM Z(又名 System z、zSeries,不管他们本周如何称呼它)实际上有一个堆(在某种程度上,你可以从操作系统分配内存)但没有堆栈。它实际上通过使用这个堆内存和某些寄存器来实现一个链表堆栈(类似于上面链接中引用的 RCA 芯片),这意味着函数 prolog 使用 STORAGE OBTAIN 分配本地函数内存,并且 epilog 使用 @ 释放它987654327@.

不用说,这会在每个函数的序言和结语中添加相当多的额外代码。

【讨论】:

【参考方案3】:

C11 标准(请参阅n1570)需要堆栈和堆,正如其他答案所解释的那样。但是,在实践中,两者都是有用的

关于call stack,这很常见,但可能会被“避免”:

一些optimizing compilers 可能足够聪明(尤其是在整个程序优化或链接时优化)来检测整个程序不需要任何东西的情况(一个简单的这种情况就是一个整体没有函数指针且没有递归的程序:在这种情况下,每个“调用帧”都可以在编译时静态分配)。许多优化编译器是inlining 一些调用(有效地消除了这些调用的调用堆栈的需要),即使对于未标记为inline 的函数也是如此。

当今的许多处理器(x86 或 x86-64、AVR、SPARC、ColdFire 即 mc68K...)都有一个硬件调用堆栈(例如,一些“stack pointer” register,函数调用已知)。在那些没有这样的硬件堆栈指针(例如IBM Z series 大型机、PowerPC、MIPS、RISC-V,或者可能是ARM)上,calling convention(或ABI)可能传统上专门使用一些寄存器来扮演堆栈指针的角色。顺便说一句,C 甚至可以在理论上为random-access machines 实现(它没有任何寄存器或堆栈指针)。

1234563 . Appel 的旧书Compiling with Continuations 详细解释了这个想法。我不能命名任何这样做的 C 编译器,但标准允许这种方法。如果你将一些编译器从 C 编码为 SML(或一些 Lisp),你就可以拥有这样的编译器。

关于 C 堆,malloc 函数总是会失败,这仍然符合标准。 I claim that such a malloc is useless,但可能是最快的。此外,C 标准允许独立实现根本没有任何 malloc

如果您寻找 C 几乎需要的特性,我会研究二进制表示。我倾向于相信为十进制计算机(如旧的IBM 1620)或三进制计算机(如Setun)实现C11编译器是非常不切实际的(但原则上这是可能的,因为你可以“emulate”三进制或十进制计算机上的二进制计算机;都是Turing-complete)。但是,您只会在博物馆中找到如此古老(1950 年代末、1960 年代初)的计算机,并且在 C 发明之前它们就消失了。

顺便说一句,调用堆栈和堆存在于 ALGOL 和 Lisp(和 IPL-V)实现中,比 C 早了几十年。

【讨论】:

有趣的研究......太糟糕了,我不能多次投票,而且每个人似乎都在周末离开了(或者也许观看了 环法自行车赛的到来)

以上是关于C 是不是需要堆栈和堆才能运行?的主要内容,如果未能解决你的问题,请参考以下文章

malloc() |堆栈和堆位置的内存地址长度的差异| C 编程

堆栈与堆栈和堆与堆

C++ 中的 RAM、硬盘、堆栈和堆是啥?

堆栈和堆内存的大小[重复]

现代计算机的堆栈和堆空间

数据结构的栈和堆和程序中的堆和栈