В общем, если вы хотите, чтобы в качестве результата команды использовалась общая строковая константа, эта команда не должна быть 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
- это команда, которая выдает буквенную строку 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
opsкоторые доставляли вам неприятности.(Также на один меньше без операции.)
Лично я бы вместо этого использовал эту несколько более эффективную версию:
proc one_or_other {v1 v2} {
if {rand() < 0.5} {
return $v1
} else {
return $v2
}
}
Я предпочитаю использовать явные return
s и избегатьвызовы функций мне не нужны.