首页 > 解决方案 > 如何从表达式中正确返回字符串常量?

问题描述

假设我有以下内容:

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}

标签: tcl

解决方案


通常,如果您想要一个通用字符串常量作为命令的结果,那么该命令最好不要是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),a tryCvtToNumeric(稍后会详细介绍)和 adone来标记这个小脚本的结束。

那么在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(即一个参数)是一个类似于的命令$xstring cat 0x123是一个产生文字字符串的命令0x123,并且if(通常被忽略)的结果是所采用的分支中脚本的结果。然后您的实际脚本变为(没有额外expr的 s):

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
    }
}

我更喜欢使用显式returns,并避免我不需要的函数调用。


推荐阅读