Tcl 何时是命令';调用了什么编译函数?

Tcl 何时是命令';调用了什么编译函数?,tcl,Tcl,我对TCL执行的理解是,如果定义了命令的编译函数,则在调用其执行函数之前,在执行命令时首先调用它 以命令append为例,下面是它在tclBasic.c中的定义: static CONST CmdInfo builtInCmds[] = { {"append", (Tcl_CmdProc *) NULL, Tcl_AppendObjCmd, TclCompileAppendCmd, 1}, 以下是我的测试脚本: $ cat

我对TCL执行的理解是,如果定义了命令的编译函数,则在调用其执行函数之前,在执行命令时首先调用它

以命令append为例,下面是它在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
我在Tcl CompileAppendCmd和Tcl_AppendObjCmd这两个函数中都添加了gdb断点。我的期望是在Tcl_AppendObjCmd之前命中TclCompileAppendCmd

Gdb的目标是tclsh8.4,参数是t.tcl

我看到的很有趣:

  • TlcCompileAppendCmd确实首先被命中,但它不是来自t.tcl, 而是来自init.tcl
  • TlcCompileAppendCmd多次被命中,它们都来自init.tcl
  • 第一次执行t.tcl时,命中的是tcl_AppendObjCmd,而不是TlcCompileAppendCmd
  • 我无法理解:

  • 为什么对init.tcl调用compile函数而对t.tcl不调用

  • 每个脚本都应该独立编译,即init.tcl处带有已编译命令append的对象不会被以后的脚本重用,不是吗

  • [更新] 感谢您提供的提示,在我将脚本移动到proc后,我可以看到TlcCompileAppendCmd被点击。

    编译函数(
    TlcCompileAppendCmd
    ,在您的示例中)由字节码编译器调用,当它想要为该特定命令的特定实例发出字节码时。如果没有命令的编译函数,字节码编译器也有一个回退:它发出指令来调用标准实现(在本例中为
    Tcl_AppendObjCmd
    ;另一个字段中的
    NULL
    会导致Tcl生成thunk,以防有人坚持使用特定的API,但您可以忽略此情况)。这是一个有用的行为,因为它是如何处理I/O之类的操作的;与执行磁盘或网络I/O的开销相比,调用标准命令实现的开销非常小

    但是字节码编译器什么时候运行

    在一个层面上,它会在Tcl的其他部分要求运行时运行。很简单!但这对您并没有真正的帮助。更重要的是,它会在Tcl在
    Tcl_Obj
    中计算尚未具有字节码类型的脚本值时运行(或者如果保存的字节码表明它用于不同的解析上下文或不同的编译纪元)除非求值请求不通过标记
    TCL_EVAL_DIRECT
    TCL_EvalObjEx
    TCL_EvalEx
    (这是Tcl_EvalObjEx的一个方便的包装器)。正是这个标志给您带来了问题

    那面旗子什么时候用

    它实际上非常简单:当一些代码被认为只运行一次时使用它,因为编译的成本大于使用解释路径的成本。Tk的
    bind
    命令特别使用它来运行替换脚本回调,但是
    源代码和mai也使用它n代码
    tclsh
    (基本上是任何使用
    Tcl\u fsevalfilex
    或其前身/wrappers
    Tcl\u FSEvalFile
    Tcl\u EvalFile
    )的代码。我不能100%确定这是否是
    源代码
    d上下文的正确选择,但现在就是这样。然而,有一个解决办法(非常!)如果你正在处理循环,那么这是值得的:你可以使用一个你立即调用的过程,或者使用一个
    应用程序(我推荐最近使用的
    应用程序),
    init.tcl
    使用这些技巧,这就是你看到它编译东西的原因


    不,我们通常不会在Tcl运行之间保存编译后的脚本。我们的编译器速度很快,这并不值得;验证加载的编译代码是否适合当前解释器的成本很高,实际上从源代码重新编译速度更快。我们当前的编译器速度很快(我正在开发一个速度较慢的工具,它可以生成非常好的代码)它包括一个提前编译器,但主要是为了商业部署而不是为了速度而覆盖代码。

    尝试将测试放在进程中。Q2有一些错误的假设。
    append
    命令是编译的。这是Tcl字节码编译器的一个怪癖。一般来说,一次一个脚本开始变长,创建一个主过程并将所有内容放入其中以便编译。EDA工具中发现的非常大的脚本除外。这些脚本遇到了一些可怕的内存限制…我的答案严格适用于8.5和8.6,但可转移到8.4