PR

 前回は,C言語がプログラムを複数の関数に分ける関数型の言語であることを説明し,制御文を使って順次/反復/分岐のロジックを作る方法を紹介しました。その前の第2回では,変数には“型”があり,浮動小数点数型では小数の計算に誤差が出ることを2進数で説明しました。そろそろ自分が作りたいプログラムを独力で作れるような気がしてきたでしょうか?

 さて,今回のテーマは変数のスコープ(scope)です。実際にプログラムを書くには,変数の型だけでなく,その働きや仕組みを理解しなくてはなりません。「変数をどこに宣言すればよいのか,なんとなくわかるようになる」――それが今回の目標です。

ローカル変数とグローバル変数の違いはスコープにあり

 「変数を定義する」ということは,「メモリーの一部を自分が付けた変数名(識別子)で扱えるようにする」ことです。例えばリスト1のプログラムをコンパイルすると,変数aが二つ宣言されているというコンパイル・エラーが表示されます。コンパイラの身になってみればわかりますね。「a=1」と代入するときに,どっちのaなのか特定できないのでエラーになるわけです。つまり,一つの関数の中に,同じ名前の変数を複数個定義することはできないということがわかります。

 リスト2はどうでしょう。main関数とoutx関数の中で,それぞれ変数aを定義しています。main関数ではaを1で初期化し,outx関数では10で初期化しています*1。このプログラムはコンパイル・エラーになりません。普通に実行でき,「10,1」と表示します(図1)。outx関数で「a=10」と値を代入してもmain関数側のaには影響がないので,二つの変数aは別物であると考えることができます。つまりmain関数で宣言した変数aはmain関数内で有効で,outx関数で宣言した変数aはoutx関数内で有効なわけです。

図1●リスト2を実行したところ
図1●リスト2を実行したところ
 
リスト1●一つの関数内に,同じ名前の変数を複数定義することはできない
リスト1●一つの関数内に,同じ名前の変数を複数定義することはできない

リスト2●二つの関数で,それぞれ同じ名前の変数を定義することは可能
リスト2●二つの関数で,それぞれ同じ名前の変数を定義することは可能

 関数内で宣言した変数のことを「ローカル変数」と呼びます。また,変数が有効であるプログラム中の範囲のことを「スコープ」と呼びます。リスト2の実行結果から言えることは,「関数内で宣言されたローカル変数のスコープはその関数の中にある」ということです。

 これに対し,関数の外で宣言した変数のことを「グローバル変数」と呼びます。例えばリスト3では,関数の外でグローバル変数aを宣言しています。順番に処理を追ってみると,

(1)main関数でaに1を代入
(2)outx関数でaに10を代入
(3)outx関数でaを表示
(4)main関数でaを表示


となるので,リスト2のように「10,1」と表示するような気がしますね。でも,結果は図2のように「10,10」と表示します。

図2●リスト3を実行したところ
図2●リスト3を実行したところ
 
リスト3●関数の外で定義した変数はグローバル変数になる
リスト3●関数の外で定義した変数はグローバル変数になる

 このことからグローバル変数のスコープは,関数の中だけという制約がなく,プログラム全体で有効,つまり「グローバル変数のスコープはプログラム全体である」ということが言えるわけです*2

変数名が同じ場合ローカル変数が優先する

 では,グローバル変数と同名のローカル変数があったらどうなるのでしょうか。リスト4を見てください。

 「int a=0」と,宣言とともに初期値として0を与えた変数aはグローバル変数です。main関数の中で1を代入し,printf関数で画面に表示しています。ポイントは,outx関数の中でもローカル変数aを宣言している点です。このプログラムをコンパイルしてもエラーにはなりません。同じ名前のグローバル変数とローカル変数を宣言することをコンパイラは許可しているからです。

 となると疑問点はただ一つ。outx関数の中で「a=10」と10を代入しているのは,グローバル変数のaなのか,ローカル変数のaなのか,どちらなのでしょう? グローバル変数のスコープはプログラム全体で,ローカル変数のスコープは関数の中ですから,スコープがぶつかっているように思えますよね。

 その答えは,実行結果を見るとすぐわかります(図3)。「10,1」の順で表示されていますね。outx関数の中で10をaに代入しても,main関数側のprintf関数の出力結果は1なので,「a=10」で10を代入されたのはローカル変数のaということになります。つまり同名の変数が宣言されてスコープが重なった場合は,ローカル変数がグローバル変数を隠ぺいするのです。同名のローカル変数が有効な範囲では,グローバル変数が雲に隠れるようなイメージだと思ってください。

 
 
リスト4●グローバル変数とローカル変数で同じ変数名を使ったコード
  リスト4●グローバル変数とローカル変数で同じ変数名を使ったコード

図3●リスト4を実行したところ
図3●リスト4を実行したところ

 ところで,先ほどローカル変数のスコープは変数を宣言した関数の中だと説明しましたが,正確に言うと,これは間違いです。関数の始まりと終わりを示す中カッコ「{ }」にはさまれたブロックの中がローカル変数のスコープです。関数ブロックの中で宣言されたローカル変数は,閉じカッコ「}」が現れるまで有効なのです。実際に確かめてみましょう。極端な例ですが,図4のように関数ブロックの中に中カッコ({ })でブロックを作り,そのブロック内だけで有効なローカル変数を定義してみます。どうなると思いますか? 1番内側の最もローカルな変数aの値から順に出力していますので,実行すると「6,5,4」と表示されるのです。


図4●スコープを確認するコード。色が重なっている部分はグローバル変数a,関数ブロック内で宣言されたローカル変数aが隠ぺいされる
図4●スコープを確認するコード。色が重なっている部分はグローバル変数a,関数ブロック内で宣言されたローカル変数aが隠ぺいされる

 変数を生き物にたとえると,宣言された変数の型に応じて必要なメモリー領域が確保されることで始まり,値を代入したり出力に使ったあと,メモリーから解放されて一生を終えます。グローバル変数はプログラムの開始時にメモリーが確保され終了時に解放されるので,静的変数(恒久的変数)とも呼びます。それに対し,ローカル変数はブロックの始まりでメモリーに確保され,ブロックの終わりで解放されます。通常は,関数ブロック内で発生し,関数が終わるときに消えてしまいます。ですから,一時的変数(自動変数)などとも呼びます。恒久的だの一時的だの,変数にもいろいろな人生(変数生?)があるのだと思えば,変数を丁寧に扱うプログラムを書けるようになるかもしれませんね。