我在c中使用tcl,一般来说,我们为每次执行tcl脚本创建一个新的命名空间。 如果我在脚本中使用proc,它会在同一个命名空间下吗? proc如何转换成字节码?它是否在调用者的相同名称空间下转换?它会在不同的命名空间中,并可能使字节码无效吗?
请您简单解释一下tcl字节码是什么以及我们如何从哈希中获取变量(局部和全局变量)以及upvar如何影响它?
非常感谢!!!
答案 0 :(得分:2)
Tcl中的每个过程都知道它的名称空间是什么;它与包含其名称的命名空间相同(总是有一个;全局命名空间称为::
)。因此,为每次执行创建一个新的命名空间可能没什么意义。
字节码本身(在Tcl源代码中tclExecute.c
中定义的一种堆栈计算机上执行)在需要时创建,通常是在您第一次执行该过程时。您可以使用tcl::unsupported::disassemble
命令打印字节码:
% proc example {x} {
return [expr {$x * 2 + 3}]
}
% puts [tcl::unsupported::disassemble proc example]
ByteCode 0x0x1008d1b10, refCt 1, epoch 15, interp 0x0x100829a10 (epoch 15)
Source "\n return [expr {$x * 2 + 3}]"...
Cmds 2, src 32, inst 9, litObjs 2, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x103028010, refCt 1, args 1, compiled locals 1
slot 0, scalar, arg, "x"
Commands 2:
1: pc 0-8, src 5-30 2: pc 0-7, src 13-29
Command 1: "return [expr {$x * 2 + 3}]"...
Command 2: "expr {$x * 2 + 3}"...
(0) loadScalar1 %v0 # var "x"
(2) push1 0 # "2"
(4) mult
(5) push1 1 # "3"
(7) add
(8) done
新版本的Tcl 8.6还支持tcl::unsupported::getbytecode
,它提供对相同类型信息的机器可读访问。你真的不想解析disassemble
的输出。
从不同的命名空间调用过程不会使该过程的字节码无效。 (为什么会这样?对于库代码而言,它的效率非常低!)但是有些操作知道如何访问外部世界。让我们用upvar
:
% proc example2 {xvar} {
upvar 1 $xvar x
return [expr {[incr x] * 2 + 3}]
}
% puts [tcl::unsupported::disassemble proc example2]
ByteCode 0x0x10300e610, refCt 1, epoch 15, interp 0x0x100829a10 (epoch 15)
Source "\n upvar 1 $xvar x\n return [expr {[incr x] * 2 +"...
Cmds 4, src 58, inst 33, litObjs 4, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x103028390, refCt 1, args 1, compiled locals 2
slot 0, scalar, arg, "xvar"
slot 1, scalar, "x"
Commands 4:
1: pc 0-12, src 5-19 2: pc 13-31, src 25-56
3: pc 22-30, src 33-55 4: pc 22-24, src 40-45
Command 1: "upvar 1 $xvar x"...
(0) push1 0 # "1"
(2) loadScalar1 %v0 # var "xvar"
(4) upvar %v1 # var "x"
(9) pop
(10) nop
(11) nop
(12) nop
Command 2: "return [expr {[incr x] * 2 + 3}]"...
(13) startCommand +19 3 # next cmd at pc 32, 3 cmds start here
Command 3: "expr {[incr x] * 2 + 3}"...
Command 4: "incr x"...
(22) incrScalar1Imm %v1 +1 # var "x"
(25) push1 2 # "2"
(27) mult
(28) push1 3 # "3"
(30) add
(31) done
(32) done
upvar
本身的操作顺序是在堆栈上推送level参数和远程变量的名称(在这种情况下来自作为参数传入的变量),然后是{{ 1}}将索引1处的局部变量表条目绑定到范围1(调用者)中的变量,该变量由来自upvar %v1
的名称调用。通过使局部变量实际上是指向另一个变量的指针来完成绑定;一旦制成,它是高效的。 xvar
命令使用类似的机制,但稍有不同(global
操作码绑定到命名命名空间中的变量。)
有一些操作会使字节码无效,但它们就像重定位具有编译功能的过程或命令一样。如果你没有使用nsupvar
,你可能不需要担心它(它完全是自动的)。
在程序中执行rename
(通常来自另一个程序)有点不同,因为那时你按名称查找变量。局部变量表的名称表是过程元数据的一部分,并且不在其中的任何变量都存储在哈希表中(当您在使用不用于其他名称的名称的过程中有upvar
时使用它)作为proc中的变量的目的;即使它不常见也会发生。)
如果您真的想要详细信息,那么就无法替代阅读Tcl源代码。字节码在很多地方生成,但它的核心是upvar
;相关的执行引擎位于tclCompile.c
。程序在tclExecute.c
,tclProc.c
中的名称空间和tclNamesp.c
中的变量中定义。可能还有其他相关的地方,但那些是主要的。