Tcl – 変数の有効範囲 -global, upvar-

この記事は変数の有効範囲の説明とプロシージャ内からプロシージャの外側で定義された変数にリンク付けするglobalコマンドとupvarコマンドの使い方を紹介します。

プロシージャのスコープ

procコマンドで作成したプロシージャは、それぞれ独立した名前空間もっています。プロシージャ内で定義した変数は、そのプロシージャ内でしか使うことが出来ません。この変数のことを局所変数(ローカル変数)といいます。

これに対して、プロシージャの外側で定義した変数のことを大域変数(グローバル変数)と言います。

変数の有効範囲(scope)

大域変数と各ブロシージャ内の局所変数は、それぞれ独立した空間で定義しているので、同じ名前の変数を定義しても問題ありません。

局所変数は、そのプロシージャが呼び出されている間のみプロシージャ内で使用されます。呼び出しが終わると局所変数は消滅します。

それに対して大域変数はunsetコマンドで削除するか、プログラムが終了するまで消滅しません。

また、プロシージャの外側で定義された大域変数は、プロシージャ内からアクセスすることが出来ません。プロシージャ内部で、大域変数を参照するには、globalコマンドまたはupvarコマンドを使います。

以下のサンプルコードは、プロシージャの内側で定義された局所変数とプロシージャの外側で定義された大域変数が、それぞれ別物であることを示す例です。

[サンプル1] proc_test7.tcl

# 同じ名前の変数

set sum 0 ;# 大域変数

proc sum { x y z} {
    set sum 0 ;# 局所変数
    set sum [expr $x + $y + $z]
    return $sum
}

[実行例]

% source proc_test7.tcl
% set sum
0

% sum 11 12 13
36

% set sum
0

プロシージャ名は変数名と同じ名前を使用する事が出来ます。

サンプル:proc_test7.tclにおいて、プロシージャのsum、大域変数のsum、局所変数のsumは、それぞれ別々のsumとして認識されます。

よってプロシージャ内の変数sumの値を書き換えても、大域変数のsumの値は変わりません。

[サンプル2] proc_test8.tcl

# 値渡し

proc swap {x y} {
    set tmp $x
    set x $y
    set y $tmp
    return "$x $y"
}

[実行例]

% source proc_test8.tcl
% set a 20
20
% set b 30
30

% swap $a $b
30 20
% puts "$a $b"
20 30

実引数である変数a,bの値はswapを呼び出した時に、仮引数の変数x,yにコピーされます。このことを値渡しと言います。

コピーした値をswapが使用するので、実引数の変数a,bには影響しません。

コピー用紙を渡して、それに何か書き込んでも原紙には何も影響しませんよね。それと同じです。

それでは実引数をプロシージャ内から直接参照すると、どうなるかやってみます。

[サンプル] proc_test9.tcl

# 大域変数を参照

proc swap {} {
    set tmp $a
    set a $b
    set b $tmp
    return "$a $b"
}

[実行例]

% source proc_test9.tcl
% set a 20
20
% set b 30
30

% swap
can not read "a": no such variable

proc_test9.tclのswapは、直接、大域変数のa, b を参照しようとしています。

直接参照すると「変数aが参照できません。そのような変数はありません。」というエラーメッセージを表示しました。

では、プロシージャ内部で大域変数を参照したい場合どうすればいいのか?

プロシージャ内部で、大域変数を参照するには、globalコマンドまたはupvarコマンドを使います。

globalコマンド

[書式]

global varname ?varname ...?

globalコマンドは、varname をローカル変数ではなく、グローバル変数であると宣言します。

このコマンドは、プロシージャ内で実行する必要があります。

[サンプル] proc_test10.tcl

# 大域変数を参照

proc swap {} {
    global a b tmp ;# 大域変数の宣言。

    set tmp $a
    set a $b
    set b $tmp
    return "$a $b"
}

[実行例]

% source proc_test10.tcl
% set a 20
20
% set b 30
30

% swap
30 20

% puts "$a $b"
30 20
% puts $tmp
20

proc_test10.tclは、変数a, b, tmp を大域変数として宣言しています。

よって、プロシージャ内で変数a, b の値を変えると、大域変数のa, b の値が変わります。

globalコマンドで指定する変数は、コマンド実行前に存在していなくても構いません。変数tmp は、大域変数として宣言しているので、swapの呼び出しが終了しても消滅せずに存在しています。

upvarコマンド

[書式]

upvar ?level? otherVar myVar ?otherVar myVar ...?

    level : 呼び出し元の階層レベル
 otherVar : 上位側のローカル変数
    myVar : 自局のローカル変数

upvarコマンドは、ローカル変数を上位側のプロシージャ内で定義されたローカル変数、あるいはグローバル変数にリンクさせます。

levelupvarコマンドを実行するカレントスコープ(現在の位置)からの相対レベルになります。省略するとデフォルトは1になっています。#番号 で指定すると、グローバルスコープ(最上位)からの絶対レベルになります。

例.

  • レベル1は呼び出し元のスコープになります。
  • レベル2は呼び出し元のさらに元のスコープになります。
  • レベル#0はグローバルスコープになります。

スコープを重ねたイメージからこのレベルの事をスタックレベル(stack level)という表現をすることもあります。

<levelのイメージ>

サンプル:upvar_test1.tcl

# 参照渡し
proc sort {lname} {
    upvar $lname num
    set num [lsort -integer $num]
}

proc go {args} {
    sort args
    return $args
}

[実行例]

% source upvar_test1.tcl
% go 30 20 10
10 20 30

go 30 20 10 を実行すると、プロシージャ:go の仮引数:args に 30 20 10 が渡されます。

sort args を実行すると、argsの値ではなく、変数名:argsをプロシージャ:sort の仮引数:lname に渡します。

このように、変数の値ではなく、変数名を渡すことを参照渡しと言います。

upvar $lname num は、呼び出し側の変数:args にローカル変数:numをリンク付け(関連付け)します。

よって、set num [lsort -integer $num] は昇順でソートしたリストを呼び出し側の変数:args に代入したことになります。

upvarコマンドを利用する事により、新しい制御構造のコマンドを作ることが出来ます。以下のサンプルは、カウンタ変数を+2増加させるプロシージャです。

サンプル:upvar_test2.tcl

# +2増加させるコマンド
proc incr2 {var} {
    upvar $var num
    set num [expr $num + 2]
}

[実行例]

% source upvar_test2.tcl
% for {set i 0} {$i < 10} {incr2 i} {puts "i = $i"}
i = 0
i = 2
i = 4
i = 6
i = 8

上のサンプルは、upvarコマンドを使うと、こういう事も出来ますよ、という一例です。

incrコマンドで incr +2 と指定すれば同じことが出来ます。

メモ

Tcl8.0で新しい名前空間を提供する機能が追加されました。

Tcl8.0までの名前空間は大域的な空間とプロシージャ内の局所的なものしかありませんでした。またプロシージャ自体は大域的なものしかありませんでした。

アプリケーションの規模が大きくなると1つの大域的なスコープの中で名前がバッティングしないようにプロシージャと大域変数を扱うのは困難です。

そこで、Tcl8.0からプロシージャと大域変数に新しい名前空間を提供する機能が追加されました。

namespace evalコマンドは新しい名前空間を作成します。新しく作成された名前空間は独立しており、その中で定義されたプロシージャや変数は、大域スコープやnamespaceで作成された他の名前空間の中のプロシージャや変数とは競合しません。

namespaceについては別途紹介します。

コメント

タイトルとURLをコピーしました