双引号变量的任何差异?

时间:2013-01-30 00:22:02

标签: tcl

以下代码:

set str "a bb ccc"
if {[string first bb "$str"] >= 0} {
    puts "yes"
}

我的大学说我不应该双引号$ str因为有性能差异,像TCL在内部使用$ str创建一个新对象。

我找不到令人信服的文件。你知道这个说法是否准确吗?

2 个答案:

答案 0 :(得分:4)

你的同事实际上是错的,因为Tcl的解析器很聪明,知道"$str"$str相同。让我们看一下生成的字节码(这是使用Tcl 8.6.0,但我们将详细介绍的部分在旧版本中实际上是相同的,一直到8.0a1):

% tcl::unsupported::disassemble script {
set str "a bb ccc"
if {[string first bb "$str"] >= 0} {
    puts "yes"
}
}
ByteCode 0x0x78710, refCt 1, epoch 15, interp 0x0x2dc10 (epoch 15)
  Source "\nset str \"a bb ccc\"\nif {[string first bb \"$str\"] >= 0} "
  Cmds 4, src 74, inst 37, litObjs 7, aux 0, stkDepth 2, code/src 0.00
  Commands 4:
      1: pc 0-5, src 1-18        2: pc 6-35, src 20-72
      3: pc 15-20, src 25-46        4: pc 26-31, src 61-70
  Command 1: "set str \"a bb ccc\""
    (0) push1 0     # "str"
    (2) push1 1     # "a bb ccc"
    (4) storeScalarStk 
    (5) pop 
  Command 2: "if {[string first bb \"$str\"] >= 0} {\n    puts \"yes\"\n}"
    (6) startCommand +30 2  # next cmd at pc 36, 2 cmds start here
  Command 3: "string first bb \"$str\""
    (15) push1 2    # "bb"
    (17) push1 0    # "str"
    (19) loadScalarStk 
    (20) strfind 
    (21) push1 3    # "0"
    (23) ge 
    (24) jumpFalse1 +10     # pc 34
  Command 4: "puts \"yes\""
    (26) push1 4    # "puts"
    (28) push1 5    # "yes"
    (30) invokeStk1 2 
    (32) jump1 +4   # pc 36
    (34) push1 6    # ""
    (36) done 

正如您所看到的(查看(17) - (19)),"$str"被编译为推送变量名称和取消引用(loadScalarStk) 。这是最优化的序列,因为没有局部变量表(即,我们不在程序中)。编译器不进行非本地优化。

答案 1 :(得分:2)

我认为你的同事是正确的:如果Tcl看到普通$str预期有一个单词,它会解析出“str”作为变量的名称,在适当的范围内查找,然后提取{ {3}}表示该变量的值,然后要求该对象生成该值的字符串表示形式。此时,字符串表示将已经可用并缓存(在对象中) - 在您的情况下,它将 - 或者它将由对象透明地生成并缓存。

如果你在一个双引号字符串中取消引用变量($str),那么Tcl就是这样:当它在一个预期有一个单词的地方看到第一个"时,它进入一种模式,它将解析以下字符,执行变量和命令替换,直到它看到下一个未转义的",此时从开头"开始累积的替换文本被认为是一个单词,它最终出现在一个(新创建的)内部对象中,表示该单词的值。

正如您所看到的,在第二个(您的)情况下,将保留包含名为“str”的变量值的原始对象的值,然后在第一种情况下将其用于构造另一个值第一个值将立即使用。

现在有一个更微妙的问题。对于它评估的脚本,Tcl只保证其解释器服从an internal object,仅此而已;其他一切都是实施细节。这些细节可能会因版本而异;例如,在Tcl 8.6中,引擎已经使用非递归评估(NRE)重新实现,虽然这些是对Tcl内部的相当根本性的更改,您现有的脚本没有注意到。

我要引导你的是,讨论隐式性能“hacks”,例如我们现在只有在应用于特定版本的运行时时才有意义。我非常怀疑Tcl目前会优化"$str"来重新使用$str中的对象,但理论上它最终可能会开始。

你的方法的真正“问题”不是性能下降,而是你似乎适用于自己的明显的自我欺骗,这导致了可疑风格的Tcl代码。让我解释。与“更常规”的语言(通常受C等影响)相反,Tcl没有特殊的字符串语法。这是因为它没有字符串文字:从文字开始在脚本中生效的每个值最初都是一个字符串。任何值的实际类型都是在运行时通过对这些值进行操作的命令定义的。为了演示,set x 10; incr x将字符串“10”放到名为“x”的变量中,然后incr命令将强制该变量“x”中的值将字符串“10”转换为它保持整数(值为10);那么这个整数将增加1(产生11)使字符串表示无效作为副作用。如果您稍后将执行puts $x,则将从整数(生成“11”)重新生成字符串表示,缓存在值中,然后打印。

因此,您采用的代码样式实际上试图使Tcl代码看起来更像Python(或Perl或您以前的语言),因为没有实际价值,并且对于经验丰富的Tcl开发人员来说也看起来很陌生。在Tcl 中使用双引号和花括号进行分组,分别用于生成字符串值和代码块 - 这些只是用于不同分组方式的特定用例。考虑阅读certain evaluation rules了解更多背景信息。

更新:在this thread中对各种类型的分组进行了很好的解释,这值得一读。