堆栈使我们可以优雅地绕过有限数量的寄存器所施加的限制。
想象一下,恰好有26个全局“寄存器a”(甚至只有8080芯片的7个字节大小的寄存器),并且您在此应用中编写的每个函数都共享此平面列表。
一个天真的开始就是将前几个寄存器分配给第一个函数,并且知道只花了3个,就为第二个函数以“ d”开头……您很快就用光了。
相反,如果您有一个比喻性的磁带,例如图灵机,则可以让每个函数通过保存正在使用的所有变量并将其转发到磁带上来启动“调用另一个函数” ,然后被调用方函数可以与许多函数混淆根据需要注册。当被调用方完成操作后,它将控制权返回给父函数,该父函数知道需要在哪里锁定被调用方的输出,然后向后播放磁带以恢复其状态。
您的基本调用框架就是这样,它由标准化的机器代码序列创建和删除,编译器围绕从一个功能到另一个功能的转换进行放置。(自从我不得不记住我的C堆栈框架以来已经有很长时间了,但是您可以通过各种方式来了解X86_calling_conventions中谁丢掉什么的职责。)
(递归很棒,但是如果您不得不在没有堆栈的情况下处理寄存器,那么您真的会很喜欢堆栈。)
我认为存储程序并支持其编译所需的硬盘空间和RAM分别增加,这就是我们使用调用堆栈的原因。那是对的吗?
尽管这些天我们可以内联更多(“更快的速度”总是很好;“更少的kb的汇编”在视频流的世界中意味着很少),主要限制在于编译器能够在某些类型的代码模式中展平。
例如,多态对象-如果您不知道将要处理的唯一一种对象,则无法展平;您必须查看对象的功能表并通过该指针进行调用……在运行时很简单,在编译时无法内联。
当现代的工具链已平整了足够多的调用者以确切知道obj的风格是什么时,它可以愉快地内联一个多态定义的函数:
class Base {
public: void act() = 0;
};
class Child1: public Base {
public: void act() {};
};
void ActOn(Base* something) {
something->act();
}
void InlineMe() {
Child1 thingamabob;
ActOn(&thingamabob);
}
在上面的代码中,编译器可以选择从InlineMe到act()内部的任何内容,保持静态内联,也不需要在运行时触摸任何vtable。
但是在对象的什么味道的任何不确定性将会把它作为一个离散函数的调用,即使相同功能的其他一些调用被内联。