Forth语言中:NONAME在定义内的行为及执行时机问询
:NONAME嵌套定义的行为探究 我尝试理解Forth语言中:NONAME被包含在另一个定义中的行为。最初我用以下代码在Forth文件被included时初始化查找表:
CREATE lookup-table 256 CELLS ALLOT HERE :NONAME -- 填充lookup-table的代码 -- ; EXECUTE \ 执行:NONAME返回地址处的定义。 DP ! \ 恢复字典指针,删除该定义。
我试图把这段逻辑封装成一个词,但在64位Linux的vfxforth和gforth中都崩溃了:
: :anon HERE :NONAME EXECUTE DP ! ;\ :anon 1 2 + ; \ 此代码在两款Forth环境中崩溃
经过测试,我找到了两个在gforth和vfxforth中都能正常运行的实现:
方案1:拆分启动与收尾词
: run[ HERE :NONAME ; \ -- a xt ; : ]run EXECUTE DP ! ; \ a xt -- ; CR run[ 1 2 + . ; ]run \ 输出3,执行正常
方案2:用IMMEDIATE词收尾
: :run HERE :NONAME ; \ -- n xt ; : run; POSTPONE ; EXECUTE DP ! ; IMMEDIATE CR :run 1 2 + . run; \ 输出3,执行正常
为了跟踪字典指针(DP)的变化,我编写了调试代码:
-1 VALUE debug : here-trace debug IF CR ." DP is : " HERE . .S THEN ; : :run HERE :NONAME here-trace ; \ -- n xt ; : run; here-trace POSTPONE ; here-trace EXECUTE here-trace DP ! ; IMMEDIATE
针对以上测试,我有三个核心疑问及对应的解答:
问题1::run结尾的分号触发了什么操作?
结合vfxforth的调试输出和test定义的编译结果分析:
- 执行
:run时,它会执行HERE :NONAME,把初始DP地址和**匿名定义的入口地址(XT)**留在栈上,同时切换到匿名定义的编译上下文——后续输入的1 2 + .会被编译到这个匿名定义里,而非:run本身的定义中。 - 当遇到
run;时,由于它是IMMEDIATE词,会立即执行以下操作:POSTPONE ;:给当前的匿名定义补上结束分号,完成编译(对应调试输出中DP从5130328涨到5130347,正好是1 2 + .编译后的19字节,和test定义的字节数完全一致)。EXECUTE:执行刚完成的匿名定义,因此输出3。DP !:用之前保存的初始DP地址恢复字典指针,相当于把匿名定义从字典中移除,所以最终HERE输出的是初始DP地址。
简言之,这个分号(run;里的POSTPONE ;)是用来结束:NONAME开启的匿名定义编译,而非结束:run的定义——:run的定义在执行:run时就已经完成了。
问题2::NONAME返回的地址何时具备有效执行性?
:NONAME会立即开启匿名定义的编译上下文,它返回的地址是这个定义的入口(XT),但此时这个定义是不完整的——只有当你用;结束该定义的编译后,这个XT才指向一个可执行的完整定义。
之前:anon崩溃的根源就在于:在:NONAME启动编译后立刻调用EXECUTE,此时匿名定义还未写入任何有效代码(甚至还没完成结构初始化),执行不完整的定义必然导致运行时崩溃。
而可行方案中,都是先通过;(或POSTPONE ;)完成匿名定义的编译,再调用EXECUTE,此时XT指向的是完整、可执行的代码块。
问题3:如何让:NONAME的动作延迟到定义的末尾而非其在定义中的实际位置执行?
核心思路是将:NONAME的编译上下文启动和定义执行、清理拆分为两个独立步骤:
- 启动
:NONAME并保存关键状态(初始DP、匿名定义的XT),同时切换到匿名定义的编译模式——后续所有代码都会被编译到这个匿名定义中,而非外层定义。 - 在需要执行的位置(即代码块末尾),先结束匿名定义的编译,再执行它,最后恢复字典指针清理临时定义。
你找到的两个方案都是这个思路的具体实现:
- 方案1(
run[/]run):run[负责启动:NONAME并返回状态,]run负责执行匿名定义并清理字典。中间的;是用来结束匿名定义的编译,确保执行时定义完整。 - 方案2(
:run/run;)::run启动:NONAME并保留状态,run;作为IMMEDIATE词,在编译阶段立即执行收尾逻辑:先完成匿名定义编译,再执行,最后恢复DP。
本质上都是利用Forth的编译/执行切换机制,让代码先被编译到临时匿名定义中,再在代码块末尾执行并清理。
内容的提问来源于stack exchange,提问作者Tls Chris

