如何从表达式中正确返回字符串常量?

时间:2019-02-10 23:27:45

标签: tcl

假设我有以下内容:

proc one_or_other {v1 v2} {
    if {[expr {round(rand())}]} {
        expr {$v1}
    } else {
        expr {$v2}
    }
}

它随机返回两个值$v1$v2中的一个。很简单它可以正常工作,直到您给它提供一个像“ 01232”这样的字符串为止,该字符串可以由expr解释为一个八进制数字。因此,one_or_other 1234 01232给您666的时间是一半。

如果我想让该函数准确地给我传递它的两个字符串之一(例如,它给我的字符串为“ 1234”或“ 01232”),我应该用expr {$v1}替换什么?

1 个答案:

答案 0 :(得分:1)

通常,如果您希望将通用字符串常量作为命令的结果,则该命令最好不要为expr。问题是expr定义的,即使没有其他操作,也可能将其结果转换为规范的数值形式。

这意味着如果将x设置为0x123,我总是 期望expr {$x}产生291


让我们稍微揭开引擎盖,看看expr {$x}的字节码反汇编:

% tcl::unsupported::disassemble script {expr {$x}}
ByteCode 0x0x7f9683041b10, refCt 1, epoch 17, interp 0x0x7f9683024410 (epoch 17)
  Source "expr {$x}"
  Cmds 1, src 9, inst 5, litObjs 1, aux 0, stkDepth 1, code/src 0.00
  Commands 1:
      1: pc 0-3, src 0-8
  Command 1: "expr {$x}"
    (0) push1 0     # "x"
    (2) loadStk 
    (3) tryCvtToNumeric 
    (4) done 

有一堆我们可以忽略的东西,但最后的操作码是将常量(即变量的名称)压入操作数堆栈,读取了在操作数堆栈上命名的变量(在与先前的操作结合使用,它可以$x),tryCvtToNumeric(稍后再介绍)和done来标记这个小脚本的结尾。

那么tryCvtToNumeric在做什么?它实现了expr的结果语义,并始终放置在其中(除非编译器可以证明不需要它,对于大多数代码实际上都是如此)。无法关闭此功能。

反汇编显示出来。 (我将跳过这里我们可以忽略的部分。)

(0) push1 0     # "tcl::mathfunc::round"
(2) push1 1     # "tcl::mathfunc::rand"
(4) invokeStk1 1 
(6) invokeStk1 2 
(8) nop 
(9) nop 
(10) jumpFalse1 +16     # pc 26
(12) startCommand +12 1     # next cmd at pc 24, 1 cmds start here
(21) loadScalar1 %v0    # var "v1"
(23) tryCvtToNumeric 
(24) jump1 +14  # pc 38
(26) startCommand +12 1     # next cmd at pc 38, 1 cmds start here
(35) loadScalar1 %v1    # var "v2"
(37) tryCvtToNumeric 
(38) done 

如您所见,其中有tryCvtToNumeric个实例;您的代码中有转换。 (还请注意,代码使用了更高效的局部变量表操作来读取变量。这很好。)


当需要通用字符串结果时,请改用其他标准Tcl命令。特别是,set x(即 one 参数)是类似于$x的命令,string cat 0x123是产生文字字符串{{1}的命令},而0x123的结果(通常被忽略)是分支中脚本的结果。这样,您的实际脚本就变成了(没有多余的if):

expr

我们通过拆卸进行检查:

proc one_or_other {v1 v2} {
    if {round(rand())} {
        set v1
    } else {
        set v2
    }
}

那是相同的代码……除了没有(0) push1 0 # "tcl::mathfunc::round" (2) push1 1 # "tcl::mathfunc::rand" (4) invokeStk1 1 (6) invokeStk1 2 (8) nop (9) jumpFalse1 +15 # pc 24 (11) startCommand +11 1 # next cmd at pc 22, 1 cmds start here (20) loadScalar1 %v0 # var "v1" (22) jump1 +13 # pc 35 (24) startCommand +11 1 # next cmd at pc 35, 1 cmds start here (33) loadScalar1 %v1 # var "v2" (35) done 操作给您带来麻烦之外。 (也要少一人。)

个人而言,我会改用效率更高的版本:

tryCvtToNumeric

我更喜欢使用显式的proc one_or_other {v1 v2} { if {rand() < 0.5} { return $v1 } else { return $v2 } } ,并避免使用不需要的函数。