我对TCL执行的理解是,如果定义了命令的编译函数,则在调用执行函数之前执行命令时首先调用它。
以命令追加为例,这里是tclBasic.c中的定义:
static CONST CmdInfo builtInCmds[] = {
{"append", (Tcl_CmdProc *) NULL, Tcl_AppendObjCmd,
TclCompileAppendCmd, 1},
这是我的测试脚本:
$ cat t.tcl
set l [list 1 2 3]
append l 4
我在两个函数TclCompileAppendCmd和Tcl_AppendObjCmd添加了gdb断点。我的期望是TclCompileAppendCmd在Tcl_AppendObjCmd之前命中。
Gdb的目标是tclsh8.4,参数是t.tcl。
我看到的很有趣:
我无法理解它:
为什么编译函数是为init.tcl调用而不是为t.tcl调用的?
每个脚本都应该独立编译,即在init.tcl附加编译命令的对象不会被重用于以后的脚本,不是吗?
[UPDATE] 感谢Brad提示,在我将脚本移动到proc之后,我可以看到TclCompileAppendCmd被命中。
答案 0 :(得分:0)
当字节码编译器想要为该特定命令的特定实例发出字节码时,编译函数(在您的示例中为TclCompileAppendCmd
)将被调用。如果命令没有编译函数,字节码编译器也有一个回退:它发出调用标准实现的指令(在这种情况下为Tcl_AppendObjCmd
;另一个字段中的NULL
会导致Tcl如果有人确实坚持使用特定的API但你可以忽略它,那么就生成一个thunk。这是一个有用的行为,因为它是如何处理像I / O这样的操作;与执行磁盘或网络I / O的开销相比,调用标准命令实现的开销非常小。
但字节码编译器什么时候运行?
在一个级别上,只要Tcl的其余部分要求运行它,它就会运行。简单!但这对你没有多大帮助。更重要的是,只要Tcl评估Tcl_Obj
中没有字节码类型的脚本值(或者如果保存的字节码表明它是针对不同的分辨率上下文或不同的编译时期),它就会运行 除非评估要求不通过标记TCL_EVAL_DIRECT
到Tcl_EvalObjEx
或Tcl_EvalEx
进行字节码编译 (这是一个方便的包装器Tcl_EvalObjEx
)。这是导致你出问题的旗帜。
该标志何时使用?
实际上非常简单:当一些代码被认为只运行一次时使用它,因为编译的成本大于使用解释路径的成本。它是特别由Tk的bind
命令用于运行替代脚本回调,但它也被source
和tclsh
的主代码使用(基本上任何使用{ {1}}或其前身/包装器Tcl_FSEvalFileEx
和Tcl_FSEvalFile
)。我不是100%确定这是否是Tcl_EvalFile
d上下文的正确选择,但它现在正在发生。但是,如果您正在处理循环,则有一个非常有用的解决方法:您可以使用您立即调用的过程或使用{{1}将代码放在source
内的编译上下文中。 (这些天我推荐后者)。 source
使用这些技巧,这就是你看到它编译的原因。
不,我们通常不会在Tcl的运行之间保存编译的脚本。我们的编译器足够快,以至于不值得;验证加载的编译代码对于当前解释器是否正确的成本足够高,以至于从源代码重新编译实际上更快。我们当前的编译器 fast (我正在开发一个生成非常好的代码的慢速编译器)。有一个来自ActiveState的商业工具套件(Tcl Dev Kit),它包含一个提前编译器,但它主要围绕用于商业部署而不是速度的覆盖代码。