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

CUIミニゲーム(高低当て)Ver.2 デバッグ編(C/C++)


今回は、前回のデバッグ編です。
次の出力結果は、前回のプログラムを私が実行してみたある時の結果です。
クリアするまでがんばったのは秘密(笑)
現在の値:8
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
2
成功!0でした。

現在の値:0
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
1
成功!7でした。

現在の値:7
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
2
成功!5でした。

現在の値:5
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
2
成功!4でした。

現在の値:4
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
2
成功!1でした。

現在の値:1
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
1
成功!9でした。

現在の値:9
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
2
成功!6でした。

現在の値:6
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
2
成功!2でした。

現在の値:2
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
1
成功!3でした。

現在の値:3
1:現在値<次回値 2:現在値>次回値
次回はどうなると思いますか?:
2
成功!1でした。

ゲームクリア!


普通にゲームクリアしているように見えますが、
おかしい場所がわかるでしょうか。
わからない人は、出てきた値をよく見てください。


答えは、
8,0,7,5,4,1,9,6,2,3,1
          ^         ^

1が二個あります。

配列のシャッフルに失敗したのでしょうか?
と思う前に、数値の数を数えてください。
8,0,7,5,4,1,9,6,2,3 ,1
1,2,3,4,5,6,7,8,9,10,11

なぜか11個あります。

配列は10個のはずですが、11個目はどこから出てきたのでしょうか。
こういうのは大体「配列領域違反」です。
改めてソースをよく見てみましょう。
<  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<10;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];
//(2)次回値を代入
      
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==10){//失敗してなければloopcntは10になるはず
      
puts("ゲームクリア!");
   }
   
//終了待ち
   
getchar();
   
return 0;
注目は(1)27行のループです。
for(loopcnt=0;loopcnt<10;loopcnt++)
は、 loopcnt が0,1,2,3,4,5,6,7,8,9の時の計10回実行されます。
このとき、 loopcnt に入る可能性のある最大値は9です。
ここで第二のポイントは(2)35行の次回値の代入式です。
next=numbers[loopcnt+1];
なので最大で numbers[9+1] 、つまり numbers[10] へのアクセスがあります。
しかし、 numbers 配列は10個、要素は0〜9であって、 numbers[10] は存在していません。

以前 解説したときは即座にアクセス違反で異常終了しました。
しかし、今回のように1要素だけはみ出した場合、たいていはアクセス違反になりません。
そして(たまたまその場所にあった)謎の値が出てきます。

このような謎の値が実行結果に混じることは、思いっきりバグなわけで、修正が必要です。

今回は数値を使い果たしている状態で次を取得しようとしたことが原因なので、
ループ数を9回になるようにすれば大丈夫です。
<  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)27行のループ数を9にしました。これで35行目で範囲外アクセスは起こりません。
(2)53行の終了条件判定も9に修正します。これを忘れると「ゲームクリア!」表示が出なくなってしまいます。

これで今回のバグは終わりです。
さて、数字当てゲームは今回で終わりにして、次回は関数についての予定です。


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

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

最終更新 2008/10/17