← Prev (04)

(06) Next →

From the Software Design Gallery 第5回

『第5回・プログラミングの歴史(5)・ローカル変数と変数宣言』

前回は、変数名について説明し、変数名には常に 名前の重複の問題があることを説明しました。 今回は、名前の重複の問題に対処する方法が どのように進化していったかについて説明します。

普段から名前の重複は起きている

違うデータなら、違う変数名が付けられるので、 変数名の重複に気をつけることはあまり無いように 思えるかもしれません。 しかし、意外と日常生活において 名前の重複が多く起きていることにお気づきでしょうか。

友人と一緒に東京から長野へスキーに行くときに、 友人が車を運転して、貴方が地図を見て道案内をする ときのことを考えてみましょう。
『八王子インターから中央道を通って、 諏訪インター近くのジャンクションで 長野道に入れば行けるね。』
この文章には名前の重複がありません。 続いて、長野に入って高速道路を降りました。
『あのガソリンスタンドを右に曲がって 国道に入ってくれ。』
この文章にも名前の重複はありません。 そして、宿が近くなってきました。
『次のガソリンスタンドを左に曲がって、 しばらくまっすぐ進めば、 宿の看板があるらしいぞ。』
この文章にも名前の重複はありません。

やはり、名前の重複はあまり無いのでしょうか? いえ、もう既に名前の重複は起きています。 わかりますでしょうか。 2つ目のせりふと3つ目のせりふの両方に、 「ガソリンスタンド」という言葉が使われています。 高速道路の近くのスタンドと、宿の近くのスタンド という違うものに対して、同じ名前(名詞) が使われているのです。

スキーに行く二人にとっては、どのガソリンスタンドを 指しているかは、迷うことはありません。 宿の近くで、高速道路の近くのガソリンスタンドで 曲がるなんてことを言うわけがないからです。 このように、同じ名前でも、場所や場面によって 実際に指しているものが暗黙的に選択されることはよくあります。

この現象に対応するしくみを プログラミング言語に取り入れなければ、 高速道路の近くのガソリンスタンド、 宿の近くのガソリンスタンド、のように いちいち長い名前を使ってプログラムを 書かなければならなくなってしまいます。 (コンピュータの言っていることが理解できなかったり、 コンピュータが途方もない解釈をするのは、 だいたいこのような暗黙的なものが正しく 処理されていないケースがほとんどです。)

定義から始まるローカル変数

同じ言葉でも、使っている場所や場面によって 無意識的に意味が変わることは、 日常の生活でもよく感じるところだと思いますが、 意識してその性質を使うことがあります。 それは、契約書などに見られる「甲」「乙」という 表現です。 または、絵のついたわかりやすい解説書に見られる、 A さんと B さんという表現です。

なぜ、甲、乙としているのかというと、 たとえば、「サービスを受ける契約者」と 「サービスの提供者」ときちんと何度も記述すると、 不必要に契約書の紙の量が多くなったり、 文章がくどくなったりするためですが、 何より、1文字でどちらのことかわかって便利なのが いちばんの理由でしょう。

契約書では、まず 「甲は、○○○とする。」「乙は△△△とする。」 というような、誰が甲で誰が乙なのかを定義しています。 そして、定義の後に本文が書かれています。 プログラミング言語でも、同様の記述をします。

void  funcA()    //  関数 A
{
  int    x;            // x は数値(int)とする
  char*  s = "abc";    // s は文字列(char*)とする

  x = strlen( s );    // そして本文である処理内容を書く
}


void  funcB()    //  関数 B
{
  int  x, y;    // x, y は数値(int)とする
                 // ただし、x は、上記 funcA の x とは別の変数である
  x = 4;
  funcA();          // funcA を実行しても、x は 4 が保持される
  y = 2 * x + 5;    // y = 13
}

x や s という単純な変数名はプログラムの別の場所で 使われている可能性が非常に高いですが、 上記のように関数の中で宣言すれば、 関数の中だけ有効になるので、 別の関数の中で宣言された変数とは、 別の記憶領域がつかわれています。

このように、関数の中だけで有効になる変数を ローカル変数と呼びます。 ローカルは地方という意味ですが、 プログラムの一部の地方で使われているため、 まさにローカルです。

昔の BASIC という言語では、関数という枠組みも ありませんでしたから、当然、ローカル変数 というものもありませんでした。 つまり、すべてがグローバル変数だったのです。 上記のように関数 B に相当するメインルーチンから、 関数 A に相当するサブルーチンをコールすると、 関数 A(サブルーチン A)の中で 変数 x が書き換わってしまって、 関数 B(メインルーチン)の y の値が間違ったものになります。

ですから、なるべくローカル変数を使うようにしていくという方針が 一般的です。中には、グローバル変数を全廃すべしという 過激なものまであります。その考えもわからなくも無いのですが、 例外のないルールは無いと言いますように、例外はあるものです。 そのへんの話は、長くなるので、いつかすることにして、 今度は、ローカル変数になったことで、名前の重複の問題が どのように変わったかを考えてみたいと思います。

なぜわざわざ宣言するのか

C 言語など最近の言語では、いちいち int i; のように 宣言しなければなりません。 しかし、昔の BASIC や FORTRAN では、宣言しなくても 変数が使えるようになっていました。

実は、Visual BASIC は、ローカル変数であっても、 変数宣言をしないで使うことができます (変数宣言を必要とするオプションを外せば)。 その場合、デフォルトの型(おそらく汎用型)が採用されます。 FORTRAN では、変数の接頭辞が I から Z なら整数型で、 その他は小数型であると指定できる構文がありました。 たとえば、X や NUM は整数型で、D や ANGLE は小数型 になります。

このような便利な機能は、現在のプログラミング言語では 使えなくなってしまっています。昔からプログラミングして いた人にとっては、変数宣言をするという作業が増えたことになり、 苦痛を感じることもあったでしょう。 ローカル変数だけでなく、グローバル変数にも宣言しなければ ならないため、果たしてどこにグローバル変数を宣言したら いいのか、迷ったことは最近のプログラマにもあると思います。 それなのに、なぜわざわざ宣言をするのでしょうか?

それは、名前の重複を確実に摘出するためです。 変数宣言をしなければならないとき、 プログラマは次のように作業をします。

  1. ある処理を実現するために、 ○○○という値を格納した変数(たとえば x)を 使ってプログラム実行内容を書く。 たとえば、入力値を x として 2 * x + 5; のように書く。
  2. x が変数として新しく登場したから、 変数宣言をしなければならないので、 int x; のように書く。

このように、新しい変数に対して変数宣言をするように プログラマに癖がつきます。 この癖がついたプログラマが、別の内容の変数を新しく 使うことにして、たまたま同じ x という名前をつけたら、 どうなるでしょうか。

void  funcA()    //  関数 A  バージョン1
{
  int    x;
  char*  s = "abc";


  x = strlen( s );

}
void  funcA()    //  関数 A  バージョン2
{
  int    x;
  char*  s = "abc";
  int   x, y;     // 追加

  x = strlen( s );
  y = 2 * x + 5;    // 追加
}

ご覧のように、x という変数の宣言が同じ関数 A の中に 2個所できました。 つまり、同じ名前の変数が2度宣言されていたら、 それは名前が重複してしまったと考えられるわけです。 それをコンパイラが自動的に摘出すれば、 確実に名前の重複が避けられるというわけです。

前節の例では、funcA 関数と funcB 関数の両方に x という名前の変数がありましたが、 別の関数で同じ名前であっても別の変数であるため、 名前が重複していても問題がありません。 しかし、同じ関数の中では、 たとえば、2 * x + 5 という式に使われる x が はたしてどちらの x なのか分からないために 問題となるのです。

ところで、前節の最後で言いましたが、 ローカル変数に変わったことで 名前の重複の問題がどのように変わったのでしょうか。 実は、グローバル変数でも変数宣言により名前の重複を 摘出することができます。 では、ローカル変数との違いは何かというと、 別の関数で名前の重複があっても、別の変数なので、 使えるということです。 もし、グローバル変数しかなかったら、 x という変数はプログラム全体で1つしか 使えないため、とても不便になります。 他人が作ったプログラムと結合するときに 変数名が重複してしまうこともありますから、 なるべくローカルにすることがここでも言えます。

C 言語では、変数宣言の中に型の指定も行っていますが、 Java Script などの言語では、型の指定が必要ありません。 その代わり、var というキーワードを使って 変数宣言であることを表現しています。

var  treeWnd = window.open( "tree.htm", "tree", attr );  // treeWnd という変数の宣言

型は、代入しているオブジェクトによって決定します。 上記の場合、window.open の返り値が window 型のオブジェクトなので、 treeWnd は、window 型です。 このように、代入しているオブジェクトによるので、 別のオブジェクトを代入したら別の型になります。

数値や文字列の変数は、変数の中に数値や文字列を格納するという 感覚でしたが、オブジェクトの変数は、オブジェクトを指し示す ポインタのような感覚です。これは、次の例でよくわかります。

var  x = 2;
var  y = x;

y = y + 1;    // この時点で、x=2, y=3
var  wndX = window.open( "tree.htm", "tree", attr );
var  wndY = wndX;

wndY.close();    // この時点で、wndX もクローズしている

このように、変数によって型が決まるのでなく、 変数に格納する値やオブジェクトによって 型が決まるようになりましたが、新しいキーワード var を 用意してまで相変わらず変数宣言をしています。 これは、もちろん、名前の重複を確実に摘出するためです。

変数が、値を格納するものだけでなく、オブジェクトを 指し示すポインタのようになってきているように、 変数(名)は、ある情報にアクセスするための 手段として使われるようになってきています。 次回は、求めているデータにどのようにすれば 到達するのかという観点に注目し、 それに基づいたデータ構造について 説明したいと思います。

← Prev (04)

(06) Next →


written by T's-Neko Jul.2000  - from Sage Plaisir 2