본문으로 바로가기

Tcl의 스코프 (Scope)

category 카테고리 없음 2024. 4. 16. 11:31

Tcl은 프로시져의 스코프(영역)와 변수의 스코프(영역)를 독립적으로 갖고 있습니다. 즉, 프로시져 이름과 변수 이름은 같아도 상관없다는 얘기가 됩니다. 프로시져를 다른 스코프로 분리하고자 한다면, namespace 나 [incr Tcl]을 사용합니다.

프로시져의 스코프

프로시져는 global 영역에서 정의되나, local 영역에서 정의되나 global 프러시저가 됩니다.

변수의 스코프

모든 프로시져의 바깥쪽에 있는 스코프를 global 스코프라 합니다. 프러시저의 스코프는 local 스코프가 됩니다. 프러시저 안에서 정의된 변수는 local 스코프의 변수가 됩니다. 프러시저 안에서 global 변수를 액세스 할시에는, global 커맨드의 선언이 필요해집니다.

set gvar "Hello World"
 
puts $gvar ;# global 선언 불필요
 
proc foo {} {
        global gvar
        puts $gvar ;# global 선언 필요
}


Tcl8.0부터, namespace의 스코프 해결 연산자(::)를 사용하여 쓰는 것도 가능합니다. 이 경우 global 선언이 필요하지 않습니다.

set gvar "Hello World"
 
proc foo {} {
        puts $::gvar
}

upvar

upvar 커맨드는, 프로시져의 호출 측의 스코프 변수를, 현재 스코프의 local 변수로 결부시키는 데 사용합니다. 아래의 예는, 변수명 gvar를 프러시저 foo로 넘겨주고, 프러시저 foo 안에서 gvar를 변경하고 있습니다.

set gvar "Hello World"
 
proc foo {arg} {
        upvar $arg var
        set var "Good Morning"
}
 
foo gvar
puts $gvar


upvar의 첫번째 인자에 number를 지정하면, 레벨을 지정할 수 있습니다. 1을 지정하면, 1개 위의 스코프 영역이 됩니다. 생략 시 디폴트는 1입니다. 또 #number를 지정하면, global 소코프에서의 상대 레벨이 됩니다. global 스코프는 #0 이 되기 때문에, global 커맨드는 아래와 같은 의미가 됩니다.

upvar #0 foo foo

uplevel


uplevel 커맨드는, upvar와 비슷합니다. uplevel은, 현재의 스코프 밖에서 커맨드를 실행할때 사용합니다. 아래의 예는, 프러시저 foo 안에서 global 스코프를 이용하고 set에 의하여, global 변수 gvar를 직접 변경하고 있습니다.

set gval {Hello World}
 
proc foo {} {
        uplevel {set gval {Good Morning}}
}
 
foo
puts $gvar


uplevel의 첫번째 인자에 number를 지정하면, 레벨을 지정할 수 있습니다. 1을 지정하면, 1개 위의 스코프 영역이 됩니다. 생략 시 디폴트는 1입니다. 또, #number를 지정하면, global 스코프에서의 상대레벨이 됩니다.

이벤트 핸들러와 변수 스코프

Tcl은 이벤트 핸들러를 사용하여, 커맨드를 실행할수 있습니다. after 커맨드, bind 커맨드, button 커맨드등이 이에 해당합니다. 이벤트 핸들러는 global 스코프에서 실행되는 점에 주의해야 합니다. 예를 들면, 아래의 예는, after로 1초 후에 커맨드가 실행됩니다. 커맨드가 실행되는 것은 global 스코프 이므로, 변수 var는 에러가 납니다.

proc foo {} {
        set var "Hello World"
        after 1000 {puts $var}
}
foo


해결책은, 변수 var를 after 커맨드로 건네주기 전에 풀어쓰는 방법입니다.

# 조금 복잡
proc foo {} {
        set var "Hello World"
        after 1000 "puts \"$var\""
}
foo

# 약간 심플
proc foo {} {
        set var "Hello World"
        after 1000 [list puts $var]
}
foo


이벤트 핸들러 커맨드를 사용하는 bind나 button 커맨드등도 마찬가입니다. 특히 (”) 를 사용한다면 커맨드 치환과 변수 치환의 결과를 예측하기 어려워지고, 이것에 따른 스크립트의 버그가 생길 가능성이 높으므로 충분히 주의하여야 할 것입니다.