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

コンパイルと実行の裏側(1)(C/C++)


今回は、 第52回 で完成させたCUIミニゲーム(高低当て)Ver.2の
コンパイルの流れを追いかけていきます。

まず、第52回のソースを再掲載します。
<  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>
< 44>
< 45>
< 46>
< 47>
< 48>
< 49>
< 50>
< 51>
< 52>
< 53>
< 54>
< 55>
< 56>
< 57>
< 58>
< 59>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void){
   
int now;//現在値
   
int sel=0;//プレイヤーの選択(選択結果)
   
int next;//次回値
   
int loopcnt;//成功回数
   
char buf[256]="";//入力バッファ
   
int cmpres=0;//現在値と次回値の比較結果
   
   
int numbers[10]={0,1,2,3,4,5,6,7,8,9};//出現させる数字配列
   
int i,n,n2;
   
//randを初期化
   
srand(time(NULL));
   
//前回の方法で数字配列をシャッフル
   
for(i=10;i;i--){
      n=rand()%i;
      n2=numbers[i-1];
      numbers[i-1]=numbers[n];
      numbers[n]=n2;
   }
   
//初期値を代入
   
now=numbers[0];
   
//ループ
   
for(loopcnt=0;loopcnt<9;loopcnt++){//(1)
      
printf("現在の値:%d\n"
         "1:現在値<次回値 2:現在値>次回値\n"
         "次回はどうなると思いますか?:"
,now);
      fgets(buf,255,stdin);
      
if((buf[0]>='1')&&(buf[0]<='2')){
         sel=buf[0]-
'0';
      }
      next=numbers[loopcnt+1];
//次回値を代入
      
if(now<next){
         cmpres=1;
      }
      
else{
         cmpres=2;
      }
      now=next;
      
if(sel==cmpres){
         printf(
"成功!%dでした。\n\n",now);
      }
      
else{
         
//成功数表示
         
printf("失敗!%dでした。\n"
            "%d回成功しました。\n"
,now,loopcnt);
         
break;//ゲーム終了
      }
   }
   
if(loopcnt==9){//(2)失敗してなければloopcntは9になるはず
      
puts("ゲームクリア!");
   }
   
//終了待ち
   
getchar();
   
return 0;
前回と違い、ソース上で定義した関数は1個のみなのでネームテーブルがボコボコと
増えるようなことにはなりませんが、同じ関数内のローカル変数がどのように置かれていくのか、
実際の流れを追いかけていきたいと思います。

注:ローカル変数の配置方法に関してはコンパイラに任されていますので、
  この通りに確保される保証はない(というよりも違う可能性の方が高い)です。
  配置方法については一例ということでご了承ください。


今回はグローバル変数の定義はないので、 main 関数ネームテーブルのみです。
冒頭にウジャウジャ定義されているので、一気に行きます。

main 関数ブロック ネームテーブル
名前型名相対ID
nowint0
selint4
nextint8
loopcntint12
bufchar[256]16
cmpresint272
numbersint[10]276
iint316
nint320
n2int324
部分的に初期化文が記述されているので、それらの処理が入ります。
まず、 int sel=0 なので、「確保位置ID+4」の int 型実体に0を代入します。
次に、 char buf[256]="" です。「確保位置ID+16」の char 型実体にまず文字列 "" として
ナル文字(0)を書き込み、残りの char 型255個分の実体に0を代入します。(初期化省略は0埋め立てのため)
次に、 int cmpres=0 なので、「確保位置ID+272」の int 型実体に0を代入します。
次に、 int numbers[10]={0,1,2,3,4,5,6,7,8,9} なので、
「確保位置ID+276」から「確保位置ID+276+4*9」までの10個に0〜9の値をそれぞれ代入します。

続いて16行目、 srand(time(NULL)) です。
srand 関数は time(NULL) の戻り値を引数にしているので、
まず time 関数を引数 NULL で呼び出し、その戻り値を使って srand 関数が呼び出されます。

続いて18行目、 for(i=10;i;i--) です。
まず i=10 なので、「確保位置ID+316」の int 型実体に10を代入し、
その結果を i 条件式( i が0以外)により、ループ継続判定をします。
この条件式が偽の場合だけ、ループ終了位置に実行座標を移動させます。

続いて19行目、 n=rand()%i です。
まず rand 関数を呼び出し、その戻り値を i (確保位置ID+316)の int 型実体の値で剰余します。
その剰余結果を、 n (確保位置ID+320)の int 型実体に代入します。

続いて20行目、 n2=numbers[i-1] です。
numbers[i-1] (確保位置ID+276+((確保位置ID+316の int 型実体の値)-1)*4)の int 型実体の値を、
n2 (確保位置ID+324)の int 型実体に代入します。

続いて21行目、 numbers[i-1]=numbers[n] です。
numbers[n] (確保位置ID+276+(確保位置ID+320の int 型実体の値)*4)の int 型実体の値を、
numbers[i-1] (確保位置ID+276+((確保位置ID+316の int 型実体の値)-1)*4)の int 型実体に代入します。

続いて22行目、 numbers[n]=n2 です。
n2 (確保位置ID+324)の int 型実体の値を、
numbers[n] (確保位置ID+276+(確保位置ID+320の int 型実体の値)*4)の int 型実体に代入します。

続いて23行目、 } です。
ここはループ終点なので「続行式とループ継続判定をやり直す位置への移動命令」が置かれています。

続いて25行目、 now=numbers[0] です。
numbers[0] (確保位置ID+276+0*4)の int 型実体の値を、
now (確保位置ID+0)の int 型実体に代入します。

続いて27行目、 for(loopcnt=0;loopcnt<9;loopcnt++) です。
まず loopcnt=0 なので、「確保位置ID+12」の int 型実体に0を代入し、
その結果を loopcnt<9 条件式により、ループ継続判定をします。

続いて28〜30行目、 printf("現在の値:%d\n"
                        "1:現在値<次回値 2:現在値>次回値\n"
                        "次回はどうなると思いますか?:",now);

です。
連続して出現する文字列定数はコンパイラが一個にまとめることになっているので、
これは以下の長い一行としてコンパイラは解釈します。
printf("現在の値:%d\n1:現在値<次回値 2:現在値>次回値\n次回はどうなると思いますか?:",now);
これは printf 関数に二個の引数(最初の文字列と now )を渡して呼び出しています。
文字列定数は const char 型の配列なので、ポインタに暗黙変換され、その値が printf 関数の第一引数に、
now (確保位置ID+0)の値が printf 関数の第二引数に、それぞれ渡して呼び出します。

続いて31行目、 fgets(buf,255,stdin) です。
これは fgets 関数に三個の引数( buf と255と stdin )を渡して呼び出しています。
buf (確保位置ID+16)はその計算された絶対ID(配列からポインタへの暗黙変換)が fgets 関数の第一引数に、
255はそのまま fgets 関数の第二引数に、
stdin の値もそのまま fgets 関数の第三引数に、それぞれ渡して呼び出します。

続いて32行目、 if((buf[0]>='1')&&(buf[0]<='2')) です。
(buf[0]>='1')&&(buf[0]<='2') 条件式は、
buf[0] (確保位置ID+16+0*1)の char 型実体の値が '1' (0x31)以上でかつ、
buf[0] (確保位置ID+16+0*1)の char 型実体の値が '2' (0x32)以下の場合、真になります。
この条件式が偽になった場合、分岐終了地点(35行)に実行位置が移動されます。

続いて33行目、 sel=buf[0]-'0' です。
buf[0] (確保位置ID+16+0*1)の char 型実体の値から '0' (0x30)を減算した値を、
sel (確保位置ID+4)の int 型実体に代入します。

続いて35行目、 next=numbers[loopcnt+1] です。
numbers[loopcnt+1] (確保位置ID+276+((確保位置ID+12の int 型実体の値)+1)*4)の int 型実体の値を、
next (確保位置ID+8)の int 型実体に代入します。

続いて36行目、 if(now<next) です。
now<next 条件式は、
now (確保位置ID+0)の int 型実体の値より next (確保位置ID+8)の int 型実体の値の方が大きい場合、真になります。
この if ブロックには対応する else が配置されているため、
条件式が偽になった場合、 else ブロック(40行)に実行位置が移動されます。

続いて37行目、 cmpres=1 です。
cmpres (確保位置ID+272)の int 型実体に1を代入します。

続いて38行目、 } です。
この if ブロックには対応する else が配置されているため、
ここには分岐終了地点(42行)に実行位置を移動する命令が置かれます。

続いて39行目、 else です。
36行目と38行目の命令によって else ブロックは最初の条件式が偽にならないと来ないようになっているので、
ここには特に命令は置かれません。

続いて40行目、 cmpres=2 です。
cmpres (確保位置ID+272)の int 型実体に2を代入します。

続いて41行目、 } です。
ブロック内で変数を作ってないので、この部分には命令は特にありません。

続いて42行目、 now=next です。
next (確保位置ID+8)の int 型実体の値を、
now (確保位置ID+0)の int 型実体に代入します。

続いて43行目、 if(sel==cmpres) です。
sel==cmpres 条件式は、
sel (確保位置ID+4)の int 型実体の値が cmpres (確保位置ID+272)の int 型実体と等しい場合、真になります。
この if ブロックには対応する else が配置されているため、
条件式が偽になった場合、 else ブロック(47行)に実行位置が移動されます。

続いて44行目、 printf("成功!%dでした。\n\n",now) です。
これは printf 関数に二個の引数(最初の文字列と now )を渡して呼び出しています。
文字列定数は const char 型の配列なので、ポインタに暗黙変換され、その値が printf 関数の第一引数に、
now (確保位置ID+0)の値が printf 関数の第二引数に、それぞれ渡して呼び出します。

続いて45行目、 } です。
この if ブロックには対応する else が配置されているため、
ここには分岐終了地点(52行)に実行位置を移動する命令が置かれます。

続いて46行目、 else です。
43行目と45行目の命令によって else ブロックは最初の条件式が偽にならないと来ないようになっているので、
ここには特に命令は置かれません。

続いて48行目、 printf("失敗!%dでした。\n"
            "%d回成功しました。\n",now,loopcnt)
 です。
これも連続する文字列定数なので、結合して以下の一行になります。
printf("失敗!%dでした。\n%d回成功しました。\n",now,loopcnt)
これは printf 関数に三個の引数(最初の文字列と now と loopcnt )を渡して呼び出しています。
文字列定数は const char 型の配列なので、ポインタに暗黙変換され、その値が printf 関数の第一引数に、
now (確保位置ID+0)の値が printf 関数の第二引数に、
loopcnt (確保位置ID+12)の値が printf 関数の第三引数に、それぞれ渡して呼び出します。

続いて50行目、 break です。
直近の break 対象ブロックは27行目からの for ループなので、
27行目からの for ループの終点である53行目に実行位置を移動する命令になります。

続いて51行目、 } です。
ブロック内で変数を作ってないので、この部分には命令は特にありません。

続いて52行目、さらに } です。
ここはループ終点なので「続行式とループ継続判定をやり直す位置への移動命令」が置かれています。

続いて53行目、 if(loopcnt==9) です。
loopcnt==9 条件式は、
loopcnt (確保位置ID+12)の int 型実体の値が9と等しい場合、真になります。
この条件式が偽になった場合、分岐終了地点(57行)に実行位置が移動されます。

続いて54行目、 puts("ゲームクリア!") です。
これは puts 関数に引数を一個(文字列定数)与えて呼び出しています。
文字列定数は const char 型の配列なので、ポインタに暗黙変換され、その値が puts 関数の引数に渡されます。

続いて55行目、 } です。
ブロック内で変数を作ってないので、この部分には命令は特にありません。

続いて57行目、 getchar() です。
引数なしで getchar 関数を呼び出します。

続いて58行目、 return 0 です。
ここには値0を呼び出し元(ここではOS)に返す準備をして、
main 関数で作成したローカル変数を破棄する命令が置かれます。

そして最後、59行目、 } です。
直前で必ず return されるため、ここに来ることはありませんが、
ブロックの終端なので main 関数ブロックで作成したローカル変数を全て破棄する命令が置かれます。


コンパイル編はここまでです。
次回は同ソースを実際にVisual C++ 6.0でコンパイルして、
その結果生成された本物の命令コードを見ていく予定です。

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

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

最終更新 2008/10/17