为什么C从不实现“堆栈扩展”以允许从调用者引用(动态调整大小的)被调用函数的堆栈变量?
这可以通过扩展调用者的堆栈帧来包含来自被调用者堆栈帧的“动态返回”变量来实现。 (您可以,但不应该使用来自调用者的alloca
实现此功能 - 它可能无法在优化中继续存在。)
e.g。如果我想返回动态大小的字符串“e”,则实现可能是:
--+---+-----+
| a | b |
--+---+-----+
callee(d);
--+---+-----+---------+---+
| a | b | junk | d |
--+---+-----+---------+---+
char e[calculated_size];
--+---+-----+---------+---+---------+
| a | b | junk | d | e |
--+---+-----+---------+---+---------+
dynamic_return e;
--+---+-----+-------------+---------+
| a | b | waste | e |
--+---+-----+-------------+---------+
(“Junk”包含返回地址和程序不可见的其他特定于系统的元数据。)
使用时会浪费一点堆栈空间。
up-side是字符串处理的简化,当前malloc
ram的任何其他函数返回指针,并希望调用者在正确的时间记住free
。
显然,在生命的这个阶段向C添加这样的功能是没有意义的,我只是对为什么这不是一个好主意感兴趣。
答案 0 :(得分:3)
可以通过多层软件返回新对象。因此,浪费的空间可能来自数十个甚至数百个函数调用。
还要考虑执行某些迭代任务的例程。在每次迭代中,它从子例程中获取一些新分配的对象,并将其插入到链接列表或其他数据结构中。这样的迭代任务可以重复数百,数千或数百万次迭代。堆栈将溢出浪费的空间。
答案 1 :(得分:2)
对你的想法有些反对意见。有些已在评论中提及。有些来自我的头脑。
C没有堆叠或堆叠帧。 C只是定义范围及其生命周期,而是留给实现标准的实现。堆栈和堆栈帧实际上是实现某些C语义的最流行方式。
C没有字符串。 C并没有这样的阵列。好吧,它确实有数组,但只要你在表达式中提到一个数组(例如一个返回表达式),数组就会衰减到指向它的第一个元素的指针。返回"字符串"或者堆栈中的数组会对语言的良好区域产生重大影响。
C确实有structs
。但是,您已经可以返回struct
。我无法告诉你它是如何完成的,因为它是一个实现细节。
您具体实施的一个问题是来电者必须知道"浪费"是。不要忘记废物将包括被叫者的堆栈框架,以及来自被调用者直接或间接调用的任何函数的浪费。返回的惯例必须包括有关废物大小的信息和指向返回值的指针。
与堆内存相比,堆栈通常非常有限,特别是在使用线程的应用程序中。在某些时候,调用者需要将返回的数组向下移动到它自己的堆栈帧中。如果数组只是堆中存储的指针,那么效率会更高,但是你已经得到了现有的模型。
答案 2 :(得分:2)
你必须意识到,堆栈的实现是由CPU和OS内核强烈决定的。这种语言在这方面没有太多发言权。限制是,例如:
X86架构的ret
指令要求存储在堆栈指针中的存储单元的返回地址。因此,顶部不会有任何其他内容(语义顶部 - 通常这是最低的地址,因为堆栈倾向于增长 down )。当然,你可以解决这个问题,但这可能会产生C程序员不愿意支付的额外开销。
堆栈指针定义实际使用的分配堆栈内存的哪个部分。当控制流程异步改变(硬件中断)时,当前CPU的寄存器通常由中断处理程序立即存储到堆栈指针下面的存储器地址中。这可以在任何时候发生,甚至在大多数内核代码中也是如此。 存储在堆栈指针指向的地方下面的任何数据都会被这个数据破坏。(从技术上讲,这不完全正确,通常有一个"红色区域&# 34;在中断处理程序可能不会写入任何数据的堆栈指针之下。但是在这里我们已经非常坚定地进入了架构设计的特性。)
销毁堆栈帧通常是向堆栈指针添加一个常量。这是您可以获得的最快的指令,通常不需要执行一个周期(它将与某些内存访问并行执行)。如果堆栈帧具有动态大小,则必须通过从内存加载堆栈指针来销毁堆栈帧,并且必须保留基本指针。这是具有显着延迟的存储器访问,以及必须保存以供使用的另一个寄存器。同样,这通常是不必要的开销。
您的提案肯定是可以实现的,但需要一些解决方法。而这些变通方法通常会降低成本。性能很小,但绝对可以衡量。这不是编译器/内核开发人员想要的,并且有充分的理由。