作为函数实例的 CallFrame 和 Runnable 有什么不同

有人觉得,其实 ago 这种 CallFrame 就是 Runnable。Runnable 也可以 implement interface,也可以有自己的 field,ago 只是把它变成了语法糖。至于 AgoFrame 我只要给 Runnable 加上 bytecode 不就可以了?

如何看待这种观点呢?

如果我需要的仅仅是 Runnable,我只要做一个胶水语言就可以了,无非就是让 Runnable 也能运用 () 调用符。

由于 ago 目前是用 Java 实现的,这的确掩盖了问题本质,很容易把实现和设计相混淆。

本质上,Java 也好,c 也好,其函数都是无法进行对象化理解的。在 Java 里,函数的 bytecode 和常量字符串相仿,存放在 meta 区,它属于类的数据,在 c 里,和汇编相似它们会放在数据段,而函数无非就是一个入口地址。
这两种模式下,调用函数均遵循 stack 模型,通过 push 压入参数,最终压入函数地址,再让 cpu pc 寄存器移动到该地址。

对于机器来说函数是不存在的,对象当然更是不存在的。

从这个过程可以看出,函数所需要的内存空间可以认为是由 push 开辟的,这导致它们始终分配于栈区。而对象的内存空间则分配于堆区,事实上相当于 malloc。

而在 ago 里,所有空间都是由 new_xx 系列指令开辟的,包括函数的内存空间。因此 ago 里的函数事实上就不是传统的栈分配的函数。用 new_xx 开辟帧内存空间的行为显示了 ago vm 作为一个纯 OO 机器的特殊性。

只有所有函数的实例都由 new_xx 指令开辟才能真正称为面向对象机器。

试想 ago 是用汇编实现的,或者类似 LISP 机我们来设计一台宏指令的 ago 机器,我们该如何组织内存?

假如 ago 采用汇编实现,类信息依然会堆放在一大块内存区,函数也是一种类,它的信息也会堆在此处。随之,伴随着一个 new_vc main#,main# 对象被创建,通过 invoke 指令,该函数对象被运行(eval)。

那么内存回收呢?传统的栈模型通过 pop 能实现返回值和函数占用的内存销毁,在 ago 里我们可以通过 return/throw 指令作为出口来回收内存。

和号称最面向对象的 smalltalk 相比,ago 都更要面向对象。在 smalltalk 里,函数被理解为一个可达的对象,调用函数就是去取用这个对象来执行。既然函数是一个对象,那么它属于什么类?它的类能定制吗?
把函数理解为对象造成在语言里有全局对象这样的怪异概念,更无法解释调用帧,当需要解释 corountine 等事物时,就更加无能为力了。

posted @ 2026-07-07 11:37  Inshua  阅读(1)  评论(0)    收藏  举报