ローカル変数をその名前によって可視できるのはどこまででしょうか? – 基本的に、 その答えは、 ローカル変数がブロック構造言語で期待される場所で、
場合によってはもうちょっとだけ長くできます。 ローカル変数のスコープを制限したい場合は、 その定義を
SCOPE
...ENDSCOPE
で囲んで下さい。
scope
( compilation – scope ; run-time – ) gforth-0.2 “scope”
endscope
( compilation scope – ; run-time – ) gforth-0.2 “endscope”
これらのワードは制御構造のワードのように動作するため、 CS-PICK
および CS-ROLL
とともに使用して、
任意の方法で範囲を制限できます。
可視性の質問に対するより正確な答えが必要な場合のために、 ここで基本原則を示します: ローカル変数は、
ローカル変数の定義を通じてのみ到達できるすべての場所で可視です30。
言い換えると、 ローカル変数の定義を経由せずに到達できる場所では不可視です。 たとえば、 IF
...ENDIF
の中で定義されたローカル変数は ENDIF
まで可視で、 BEGIN
...UNTIL
内で定義されたローカル変数は UNTIL
の後(たとえば、後続の ENDSCOPE
まで)で可視です。
このソリューションの背景にある理由は次のとおりです: 私達は、 意味がある限り、 ローカル変数を可視させたいと考えています。 ユーザーは、 明示的なスコープを使用することで、 いつでも可視性を短くすることができます。 ローカル変数の定義によってのみ到達できる場所では、 ローカル変数名の意味は明らかです。 他の場所ではそうではありません。 ローカル変数定義が含まれていない制御フロー・パスでローカル変数はどのように初期化されるのでしょうか? 2 つの独立した制御フロー パスで同一ローカル変数名が 2 回定義されている場合、 それはどちらのローカル変数を意味するのでしょうか?
上記で、 ほぼすべてのユーザーにとって十分詳細であるため、 このセクションの残りの部分はスキップしてかまいません。 本当にすべての血みどろの詳細とオプションを知る必要がある場合は、 以下を読み続けてください。
このルールを実装するには、 コンパイラーはどの場所が到達不能(unreachable)であるかを認識する必要があります。 AHEAD
や
AGAIN
や EXIT
や LEAVE
の後で、 これが自動的に認識されます。 他の場合(例: ほとんどの
THROW
の後)、 UNREACHABLE
というワードを使用して、
制御フローがその場所に到達しないことをコンパイラーに伝えることができます。 UNREACHABLE
が使用できる場所で使用されなかった場合、 唯一の結果は、 一部のローカル変数の可視性が上記のルールに記載されているよりも制限されることです。
UNREACHABLE
を使用すべきではない場所で使用すると(つまり、 コンパイラに嘘をついた場合)、 バグのあるコードが生成されます。
UNREACHABLE
( – ) gforth-0.2 “UNREACHABLE”
このルールのもう 1 つの問題は、 BEGIN
で、 どのローカル変数が incoming back-edge
で可視されるかをコンパイラーが認識できないことです。 以下で説明するすべての問題は、 コンパイラーのこの無知が原因です(BEGIN
ループを例として使用してこの問題について説明します。 この説明は ?DO
および他のループにも当てはまります)。
おそらく最も陰険な例は以下のとおりです:
AHEAD BEGIN x [ 1 CS-ROLL ] THEN {: x :} ... UNTIL
これは、 可視性ルールに従って合法である必要があります。 x
の使用には、 定義を介してのみ到達できます。 ただし、
下記に明示した使用法でなければなりません。
この例から、 可視性ルールを完全に実装するには大きな問題が伴うことが明らかです。 私たちの実装は、 一般的なケースを宣伝どおりに扱い、
例外は安全な方法で処理されます。 コンパイラーは、 BEGIN
の後に可視できるローカル変数について合理的な推測を行います。
悲観的すぎると、 ローカル変数が定義されていないという偽のエラーがユーザーに表示されます。 コンパイラーが楽観的すぎる場合、 後でこれに気づき、
警告を発行します。 上記の場合、 コンパイラーは x
が使用時に未定義であることについて文句を言います。
このセクションのあいまいな例から、 コンパイラーをトラブルに陥らせるには非常に特殊な制御構造が必要であることがわかりますが、
それでもコンパイラーは多くの場合問題なく動作します。
BEGIN
がそれより上から到達可能な場合、 最も楽観的な推測は、 BEGIN
の前に可視であるすべてのローカル変数も
BEGIN
の後にも可視であることです。 この推測は、 BEGIN
経由でのみ入るすべてのループ、 特に通常の
BEGIN
...WHILE
...REPEAT
および BEGIN
...UNTIL
ループに対して有効であり、 コンパイラーに実装されています。 BEGIN
への分岐が AGAIN
または
UNTIL
によって最終的に生成されると、 コンパイラーは推測をチェックし、 それが楽観的すぎる場合はユーザーに警告します:
IF {: x :} BEGIN \ x ? [ 1 cs-roll ] THEN ... UNTIL
ここで、 x
は BEGIN
までのみ存続しますが、 コンパイラーは THEN
まで存続すると楽観的に想定します。 UNTIL
をコンパイルするときにこの違いに気づき、 警告を発行します。 ユーザーは警告を回避し、
明示的なスコープを使用して x
が間違った領域で使用されていないことを確認できます:
IF SCOPE {: x :} ENDSCOPE BEGIN [ 1 cs-roll ] THEN ... UNTIL
推測は楽観的であるため、 未定義のローカル変数に関する偽のエラー・メッセージは表示されません。
BEGIN
がそれより上から到達可能でない場合(たとえば、 AHEAD
または EXIT
の後)、
BEGIN
の後で定義されたローカル変数の可視については、 コンパイラーは楽観的な推測を行うことさえできません。
悲観的に、 制御構造の外側の最新の場所(つまり、 制御フロー・スタック上に何もない場所)で可視であったすべてのローカル変数が可視であると仮定します。 これは以下のことを意味します:
: foo IF {: z :} THEN {: x :} AHEAD BEGIN ( * ) [ 1 CS-ROLL ] THEN {: y :} ... UNTIL ;
ここで、 ( * )
でマークされた場所では、 x
は可視ですが、 y
は不可視です(ただし、
到達可能性ルールによれば、 可視であるべきです)。 z
はそこでは不可視で、 可視であるべきではありません。
ただし、 ASSUME-LIVE
を使用すると、 最上位の制御フロー・スタック項目が作成された時点と同じローカル変数が BEGIN
で可視になるのだと、 コンパイラーに想定させることができます。
ASSUME-LIVE
( orig – orig ) gforth-0.2 “ASSUME-LIVE”
例えば、 以下のように使います
IF {: x :} AHEAD ASSUME-LIVE BEGIN x [ 1 CS-ROLL ] THEN ... UNTIL THEN
ここで、 x
のローカル変数定義は制御構造内にあるため、 x
を使用した時点では x
は可視ではありませんが、
ASSUME-LIVE
を使用することで、 プログラマはコンパイラーに AHEAD
の時点で可視である、
そのローカル変数が、 BEGIN
の時点でも可視であるべきであると伝えます。
BEGIN
の前にローカル変数が定義されている他のケースは、 ASSUME-LIVE
の前に適切な
CS-ROLL
を挿入する(そして ASSUME-LIVE
の背後にある制御フロー・スタック操作を変更する)ことで処理できます。
ローカル変数が BEGIN
の後で定義されている場合(ただし、 BEGIN
の直後で可視である必要があります)は、
ループを再配置することによってのみ処理できます。 たとえば、上記の「最も陰険な」例は以下のように整理できます:
BEGIN {: x :} ... 0= WHILE x REPEAT