[前へ]   [目次へ]   [次へ]

ネームテーブルの参照順序(2)(C/C++)

今回は、「ネームテーブルの参照順序」実行時編です。
各部で参照する実体はコンパイル時に指定された通りなので、
前回を別ウィンドウで開いて 対比しながら読むといいかもしれません。

コンパイル時までは前回やったので、今回は実行時編です。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
< 35>
< 36>
< 37>
< 38>
< 39>
< 40>
< 41>
< 42>
< 43>
#include <stdio.h>

int n=0;//(1)グローバルネームテーブルのn

int func1(void){//(2)
   
int n;//(3)func1ネームテーブルのn
   
n=10;//(4)
   
printf("func1:%d\n",n);//(5)
   
return n;//(6)
}//(7)
int func2(void){//(8)
   
if(n){//(9)
      
int n;//(10)func2内のifネームテーブルのn
      
n=50;//(11)
      
printf("func2-1:%d\n",n);//(12)
   }//(13)
   
printf("func2-2:%d\n",n);//(14)
   
n=20;//(15)
   
printf("func2-3:%d\n",n);//(16)
   
return n;//(17)
}//(18)
int func3(void){//(19)
   
printf("func3:%d\n",n);//(20)
   
return n;//(21)
}//(22)
int main(void){//(23)
   
int n;//(24)mainネームテーブルのn
   
n=30;//(25)
   
func1();//(26)
   
func2();//(27)
   
func3();//(28)
   
printf("main1:%d\n",n);//(29)
   
if(n){//(30)
      
int n;//(31)main内のifネームテーブルのn
      
n=40;//(32)
      
printf("func1():%d\n",func1());//(33)
      
printf("func2():%d\n",func2());//(34)
      
printf("func3():%d\n",func3());//(35)
      
printf("main2:%d\n",n);//(36)
   }//(37)
   
printf("main3:%d\n",n);//(38)
   
return 0;
}
//(39) 
実行結果:
func1:10
func2-2:0
func2-3:20
func3:20
main1:30
func1:10
func1():10
func2-1:50
func2-2:20
func2-3:20
func2():20
func3:20
func3():20
main2:40
main3:30

プログラムの実行が開始されると、
まず最低限必要なDLLなどがロードされ、ライブラリの使用準備をします。
続いてローカル変数用に使う領域が確保されます。
ローカル変数はここで確保した領域を後で再分配して割り当てています。

ここまではコンパイラが実行ファイルを作る時に自動的に処理を組み込んでいるので、
書いたプログラムが影響を与えるのはここからです。

まず、グローバル変数の領域を確保します。
グローバル変数はプログラムの開始時から終了時までずっと存在しているので、
大抵のコンパイラはローカル変数用の領域とは別の領域を割り当てます。

今回は、3行目で定義したグローバル変数 n が最初に作られます。
初期化により0を代入していますが、グローバル変数に限り、
初期化をしなくても0になることになっているので、
この場合は実は初期化しなくても値が0であることは保証されていたりします。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int0グローバル(n,line 3)

今回からリストに項目「所属」を追加しました。
これはコンパイル時に所属していたネームテーブル(グローバル)と、
ソース上の名前(n)、この実体を定義した行番号(line 3)です。

また、前回の表記から更新された部分は赤で、その時参照された部分は青で表示します。


これが終わると、またイロイロと前準備が行われた後、 main 関数が呼び出されます。
というわけで、(23)26行目から実行開始です。

(24)27行目、 main ブロックのローカル変数 n が作られます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int0グローバル(n,line 3)
0x0013FF7Cint不定(未初期化)main(n,line 27)
現在位置:27行終了(main)
mainブロック 確保位置ID:0x0013FF7C



続いて(25)28行目、 n=30 です。
ここでの n はコンパイルした時にどれを参照するかが指定されています。
前回の通り、ここでは n は main ブロックの n です。
なので、 main ブロックの確保位置ID 0x0013FF7C + n の相対ID 0が足し合わされ、
0x0013FF7C に int 型実体があるものとして処理します。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int0グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
現在位置:28行終了(main)
mainブロック 確保位置ID:0x0013FF7C



続いて(26)29行目、 func1() です。
関数呼び出し(引数なし)なので、実行位置が(2)5行目に移動されます。


(3)6行目、 func1 ブロックのローカル変数 n が作られます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int0グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF20int不定(未初期化)func1(n,line 6)
現在位置:6行終了(func1)
mainブロック 確保位置ID:0x0013FF7C
func1ブロック 確保位置ID:0x0013FF20



続いて(4)7行目、 n=10 です。
ここでは n は func1 ブロックの n です。
なので、 func1 ブロックの確保位置ID 0x0013FF20 + n の相対ID 0が足し合わされ、
0x0013FF20 に int 型実体があるものとして処理します。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int0グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF20int10func1(n,line 6)
現在位置:7行終了(func1)
mainブロック 確保位置ID:0x0013FF7C
func1ブロック 確保位置ID:0x0013FF20



続いて(5)8行目、 printf("func1:%d\n",n) です。
先ほど同様、 n は 0x0013FF20 の実体として扱われます。
ここでの printf 関数の呼び出しにより、最初の出力が発生します。
ここで出力されるのは 0x0013FF20 の実体の値なので 10 が出力されます。


続いて(6)9行目、 return n です。
先ほど同様、 n は 0x0013FF20 の実体として扱われます。
return されるので、 func1 を呼び出した位置(26)29行目に位置が戻されます。
戻り値は受け手側で使われていないので、破棄されます。
さらに、 func1 ブロックのローカル変数が破棄されるので、 0x0013FF20 の実体が消滅します。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int0グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
現在位置:29行終了(main)
mainブロック 確保位置ID:0x0013FF7C



続いて(27)30行目、 func2() です。
関数呼び出し(引数なし)なので、実行位置が(8)11行目に移動されます。


続いて(9)12行目、 if(n) です。
ここでの n はグローバル変数の n(0x0042C748) です。
グローバル変数は最初から位置が決められているので、実行時にも絶対IDで参照されます。
ので、 0x0042C748 の実体が0以外であれば真です。
が、 0x0042C748 の実体の値は0なので、この条件式は偽になり、
実行位置はブロック終了位置(13)16行に移動されます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int0グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
現在位置:16行終了(func2)
mainブロック 確保位置ID:0x0013FF7C



続いて(14)17行目、 printf("func2-2:%d\n",n) です。
先ほど同様、 n は 0x0042C748 の実体として扱われます。
ここでの printf 関数の呼び出しにより、2回目の出力が発生します。
ここで出力されるのは 0x0042C748 の実体の値なので 0 が出力されます。


続いて(15)18行目、 n=20 です。
先ほど同様、 n は 0x0042C748 の実体として扱われます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
現在位置:18行終了(func2)
mainブロック 確保位置ID:0x0013FF7C



続いて(16)19行目、 printf("func2-3:%d\n",n) です。
先ほど同様、 n は 0x0042C748 の実体として扱われます。
ここでの printf 関数の呼び出しにより、3回目の出力が発生します。
ここで出力されるのは 0x0042C748 の実体の値なので 20 が出力されます。


続いて(17)20行目、 return n です。
先ほど同様、 n は 0x0042C748 の実体として扱われます。
return されるので、 func2 を呼び出した位置(27)30行目に位置が戻されます。
戻り値は受け手側で使われていないので、破棄されます。


続いて(28)31行目、 func3() です。
関数呼び出し(引数なし)なので、実行位置が(19)22行目に移動されます。


続いて(20)23行目、 printf("func3:%d\n",n) です。
ここでの n はグローバル変数の n(0x0042C748) です。
ここでの printf 関数の呼び出しにより、4回目の出力が発生します。
ここで出力されるのは 0x0042C748 の実体の値なので 20 が出力されます。


続いて(21)24行目、 return n です。
先ほど同様、 n は 0x0042C748 の実体として扱われます。
return されるので、 func3 を呼び出した位置(28)31行目に位置が戻されます。
戻り値は受け手側で使われていないので、破棄されます。


続いて(29)32行目、 printf("main1:%d\n",n) です。
ここでの n は main ブロックの n(0x0013FF7C) です。
ここでの printf 関数の呼び出しにより、5回目の出力が発生します。
ここで出力されるのは 0x0013FF7C の実体の値なので 30 が出力されます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
現在位置:32行終了(main)
mainブロック 確保位置ID:0x0013FF7C



続いて(30)33行目、 if(n) です。
先ほど同様、 n は 0x0013FF7C の実体として扱われます。
0x0013FF7C の実体の値は30なので、この条件式は真になり、
実行位置はブロック内(31)34行に入っていきます。


続いて(31)34行目、 main内のif(main-if) ブロックのローカル変数 n が作られます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int不定(未初期化)main-if(n,line 34)
現在位置:34行終了(main)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78



続いて(32)35行目、 n=40 です。
ここでの n は main-if ブロックの n(0x0013FF78) です。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
現在位置:35行終了(main)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78



続いて(33)36行目、 printf("func1():%d\n",func1()) です。
この printf 関数の呼び出しは func1 関数の戻り値を表示します。
なのでまず、 func1 関数の戻り値を取得するために func1 関数を呼び出します。
func1 関数を呼び出したので、実行位置は(2)5行に移動されます。


(3)6行目、 func1 ブロックのローカル変数 n が作られます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
0x0013FF20int不定(未初期化)func1(n,line 6)
現在位置:6行終了(func1)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78
func1ブロック 確保位置ID:0x0013FF20



続いて(4)7行目、 n=10 です。
ここでの n は func1 ブロックの n(0x0013FF20) です。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
0x0013FF20int10func1(n,line 6)
現在位置:7行終了(func1)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78
func1ブロック 確保位置ID:0x0013FF20



続いて(5)8行目、 printf("func1:%d\n",n) です。
ここでの n は func1 ブロックの n(0x0013FF20) です。
ここでの printf 関数の呼び出しにより、6回目の出力が発生します。
ここで出力されるのは 0x0013FF20 の実体の値なので 10 が出力されます。


続いて(6)9行目、 return n です。
先ほど同様、 n は 0x0013FF20 の実体として扱われます。
return されるので、 func1 を呼び出した位置(33)36行目に位置が戻されます。
戻り値用の一時変数に 0x0013FF20 の実体の値がコピーされ、
func1 ブロックのローカル変数が破棄されるので、 0x0013FF20 の実体が消滅します。


そして(33)36行目の処理が続行され、 printf 関数を続いて呼び出します。
ここでの printf 関数の呼び出しにより、7回目の出力が発生します。
ここで出力されるのは func1 関数の戻り値の値なので 10 が出力されます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
現在位置:36行終了(main)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78



続いて(34)37行目、 printf("func2():%d\n",func2()) です。
先ほど同様この printf 関数の呼び出しは func2 関数の戻り値を表示します。
なのでまず、 func2 関数の戻り値を取得するために func2 関数を呼び出します。
func2 関数を呼び出したので、実行位置は(8)11行に移動されます。


続いて(9)12行目、 if(n) です。
ここでの n はグローバル変数の n(0x0042C748) です。
前回と異なり、今回は 0x0042C748 の実体の値は20なので、この条件分岐は真になります。
そのため、実行位置はブロック内(10)13行に入っていきます。


続いて(10)13行目、 func2内のif(func2-if) ブロックのローカル変数 n が作られます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
0x0013FF20int不定(未初期化)func2(n,line 13)
現在位置:13行終了(func2)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78
func2-ifブロック 確保位置ID:0x0013FF20



続いて(11)14行目、 n=50 です。
ここでの n は func2-if ブロックの n(0x0013FF20) です。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
0x0013FF20int50func2(n,line 13)
現在位置:14行終了(func2)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78
func2-ifブロック 確保位置ID:0x0013FF20



続いて(12)15行目、 printf("func2-1:%d\n",n) です。
先ほど同様、 n は 0x0013FF20 の実体として扱われます。
ここでの printf 関数の呼び出しにより、8回目の出力が発生します。
ここで出力されるのは 0x0013FF20 の実体の値なので 50 が出力されます。


続いて(13)16行目、 func2-if ブロックの終点です。
func2-if ブロックのローカル変数が破棄されるので、 0x0013FF20 の実体が消滅します。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
現在位置:16行終了(func2)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78



続いて(14)17行目、 printf("func2-2:%d\n",n) です。
ここでの n はグローバル変数の n(0x00427C48) です。
ここでの printf 関数の呼び出しにより、9回目の出力が発生します。
ここで出力されるのは 0x00427C48 の実体の値なので 20 が出力されます。


続いて(15)18行目、 n=20 です。
先ほど同様、 n は 0x00427C48 の実体として扱われます。
しかし 0x00427C48 の実体の値は既に20なので、見掛け上は特に何も起こりません。(代入処理自体は行われます)


続いて(16)19行目、 printf("func2-3:%d\n",n) です。
ここでの n はグローバル変数の n(0x00427C48) です。
ここでの printf 関数の呼び出しにより、10回目の出力が発生します。
ここで出力されるのは 0x00427C48 の実体の値なので 20 が出力されます。


続いて(17)20行目、 return n です。
先ほど同様、 n は 0x00427C48 の実体として扱われます。
return されるので、 func2 を呼び出した位置(34)37行目に位置が戻されます。
また、戻り値用の一時変数に 0x00427C48 の実体の値がコピーされます。


そして(34)37行目の処理が続行され、 printf 関数を続いて呼び出します。
ここでの printf 関数の呼び出しにより、11回目の出力が発生します。
ここで出力されるのは func2 関数の戻り値の値なので 20 が出力されます。


続いて(35)38行目、 printf("func3():%d\n",func3()) です。
先ほど同様この printf 関数の呼び出しは func3 関数の戻り値を表示します。
なのでまず、 func3 関数の戻り値を取得するために func3 関数を呼び出します。
func3 関数を呼び出したので、実行位置は(19)22行に移動されます。


続いて(20)23行目、 printf("func3:%d\n",n) です。
ここでの n はグローバル変数の n(0x00427C48) です。
ここでの printf 関数の呼び出しにより、12回目の出力が発生します。
ここで出力されるのは 0x00427C48 の実体の値なので 20 が出力されます。


続いて(21)24行目、 return n です。
先ほど同様、 n は 0x00427C48 の実体として扱われます。
return されるので、 func3 を呼び出した位置(35)38行目に位置が戻されます。
また、戻り値用の一時変数に 0x00427C48 の実体の値がコピーされます。


そして(35)38行目の処理が続行され、 printf 関数を続いて呼び出します。
ここでの printf 関数の呼び出しにより、13回目の出力が発生します。
ここで出力されるのは func3 関数の戻り値の値なので 20 が出力されます。


続いて(36)39行目、 printf("main2:%d\n",n) です。
ここでの n は main-if ブロックの n(0x0013FF78) です。
ここでの printf 関数の呼び出しにより、14回目の出力が発生します。
ここで出力されるのは 0x0013FF78 の実体の値なので 40 が出力されます。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
0x0013FF78int40main-if(n,line 34)
現在位置:39行終了(main)
mainブロック 確保位置ID:0x0013FF7C
main-ifブロック 確保位置ID:0x0013FF78



続いて(37)40行目、 main-if ブロックの終点です。
main-if ブロックのローカル変数が破棄されるので、 0x0013FF78 の実体が消滅します。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
0x0013FF7Cint30main(n,line 27)
現在位置:40行終了(main)
mainブロック 確保位置ID:0x0013FF7C



続いて(38)41行目、 printf("main3:%d\n",n) です。
ここでの n は main ブロックの n(0x0013FF7C) です。
ここでの printf 関数の呼び出しにより、15回目の出力が発生します。
ここで出力されるのは 0x0013FF7C の実体の値なので 30 が出力されます。


そして42行目、 return 0 です。
この return により、 main 関数が終了してプログラムは終了処理に入ります。
その前に main ブロックのローカル変数が破棄されるので、 0x0013FF7C の実体が消滅します。

この時存在する変数実体(このソースで定義していないものは省略)
実体ID(絶対)実体の型保持値所属
0x00427C48int20グローバル(n,line 3)
現在位置:42行途中(main)

ここから先はまたコンパイラが自動的に処理を組み込んでいます。
終了処理に入るとまずグローバル変数を消滅させます。
そしてローカル変数用に確保したメモリ領域を開放し、
読み込んだDLLなどにプログラムが終了することを通知します。
その後、ライブラリの終了処理を行います。

全ての作業が終わると、プログラムが終了してフィニッシュになります。



2ページに渡る長丁場、お疲れ様でした。
次回は 第52回 で作りきったCUIミニゲーム(高低当て)Ver.2を今回同様の手順で追いかけてみます。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2008/10/17