【第8回】VisualStudioでデバッグを行うには?

次回の記事

terapotan.hatenablog.jp

前回の記事

terapotan.hatenablog.jp

連載記事一覧

terapotan.hatenablog.jp

デバッグを行うプログラム

今回は実際のコードを使ってデバッグを行っていきます。

以下これからデバッグを行うコードを提示します。新たにプロジェクトを作成して「main.cpp」「judge.cpp」「judge.h」を次のように入力してください。
main.cpp

#include "pch.h"

#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include <stdio.h>
#include "judge.h"

int getSpecRangeNum(int min, int max);

int main(void) {
    int userHand = 0;
    int computerHand = 0;
    printf("*****じゃんけんゲーム*****\n");
    printf("あなたの手を入力してください。(0:グー,1:チョキ,2:パー)\n");
    scanf("%d", &userHand);

    computerHand = getSpecRangeNum(0,3);

    switch (computerHand) {
    case ROCK:
        printf("コンピュータの手:グー\n");
        break;
    case SCIS:
        printf("コンピュータの手:チョキ\n");
        break;
    case PAPER:
        printf("コンピュータの手:パー\n");
        break;

    }

    switch (judgeUserWinLoseDraw(userHand, computerHand)) {
    case WIN:
        printf("あなたの勝ち\n");
        break;
    case LOSE:
        printf("あなたの負け\n");
        break;
    case DRAW:
        printf("引き分け\n");
        break;
    }

    return 0;
}

int getSpecRangeNum(int min, int max)
{
    return min + (int)(rand()*(max - min + 1.0) / (1.0 + RAND_MAX));
}

judge.cpp

#include "pch.h"
#include"judge.h"

/*
ROCK:グー(0)
SCIS:チョキ(1)
PAPER:パー(2)
以上の定義はjudge.h参照。
*/
int judgeUserWinLoseDraw(int userHand, int enemyHand) {
    int matchResult = VALUE_OUT_OF_LANGE_ERROR;

    if (userHand == ROCK) {
        switch (enemyHand) {
        case ROCK:
            matchResult = WIN;
            break;
        case SCIS:
            matchResult = WIN;
            break;
        case PAPER:
            matchResult = LOSE;
            break;
        }
    }
    else if (userHand == SCIS) {
        switch (enemyHand) {
        case ROCK:
            matchResult = LOSE;
            break;
        case SCIS:
            matchResult = WIN;
            break;
        case PAPER:
            matchResult = WIN;
            break;
        }
    }
    else if (userHand == PAPER) {
        switch (enemyHand) {
        case ROCK:
            matchResult = WIN;
            break;
        case SCIS:
            matchResult = LOSE;
            break;
        case PAPER:
            matchResult = DRAW;
            break;
        }
    }

    return matchResult;
}

judge.h

#pragma once
#define ROCK 0
#define SCIS 1
#define PAPER 2

#define WIN 0
#define LOSE 1
#define DRAW 2

#define VALUE_OUT_OF_LANGE_ERROR -1

int judgeUserWinLoseDraw(int userHand, int enemyHand);

プログラムの説明

上のプログラムは簡単なじゃんけんゲームになっており、ビルドして実行すると次のような実行結果が表示されます。

*****じゃんけんゲーム*****
あなたの手を入力してください。(0:グー,1:チョキ,2:パー)
2
コンピュータの手:グー
あなたの勝ち

何回か実行して、実行結果を確かめてみましょう。

バグ?

何回か実行すると対戦結果がおかしくなる場合があるのに気づくはずです。

あなたの手を入力してください。(0:グー,1:チョキ,2:パー)
1
コンピュータの手:チョキ
あなたの勝ち
あなたの手を入力してください。(0:グー,1:チョキ,2:パー)
0
コンピュータの手:グー
あなたの勝ち

本来であれば、どちらの対戦結果も「引き分け」にならなければいけません。

どこにバグが潜んでいるのかをVisualStudioに搭載されているデバッガを使って突き止めていきましょう。

予備知識

先ほどのデバッグを行う前に、VisualStudioのデバッガを扱うのに必要な知識を少し見ていくことにしましょう。

ステップイン

下の画像を参考にして、main関数の最初の行にブレークポイントを打ちF5キーを押してください。

alt

デバッグ実行が行われブレークポイントを設定した行で実行が止まっています。
ここから次の行へ実行を進めたい場合はF11キーを押します。これをステップインと言います。
alt

上の画像はステップインを実行した後の画面です。現在実行している行を示す黄色い矢印が次の行に移動していることが確認できます。

変数の値を確認する

ステップインによってint userHand=0が実行されたため、userHandの値は0になっているはずです。
userHandの値を確認してみましょう。
userHandの箇所にマウスを当てるとuserHandの値が表示されます。
alt

確かに0と表示されています。

ステップオーバー

何回かステップインを実行していくと、getSpecRangeNum関数がある行にたどり着きます。
そのままステップインを実行してしまうとgetSpecRangeNum関数の内部に飛んでしまいます。

このような時には、F10キーを押してgetSpecRangeNum関数の内部に飛ばないようにします。 これをステップオーバーと言います。

getSpecRangeNum関数がある行でステップオーバーを行うと、getSpecRangeNum関数の内部に飛ばすにそのまま次の行にステップインします。(ただし処理は実行されます。)

alt

ステップアウト

また何回かステップインを実行していくと、judgeUserWinLoseDraw関数がある行にたどり着きます。
今回は、そのままステップインを実行してみましょう。
alt

judgeUserWinLoseDraw関数の内部に飛びました。

デバッグ実行中一旦関数から抜け出して、呼び出し元に戻りたいときがあるかもしれません。
そのような時はShift+F11キーを押します。
これをステップアウトと言います。
alt

上の画像はステップアウトを実行した後の画面です。呼び出し元に実行行が戻っていることがわかります。

現在の実行行を設定する

ステップイン・ステップアウト・ステップオーバーと現在の実行行を移動する方法を紹介してきましたが、時には任意の行に実行行を移動させたいときがあるかもしれません。

その場合、黄色い矢印(現在の実行行)を移動させたい行までドラッグして移動させます。
alt

デバッグをやってみる

以上の予備知識を基にじゃんけんプログラムのデバッグを行ってみましょう。 ブレークポイント予備知識で設定したブレークポイントのままで構いません。

実行行を移動する

switch文以前の行では、変数の初期化・入力などを行っておりこれ以降の作業で支障が出るためmain関数の最初の行から、実行行を移動させます。

現在の実行行を設定するを参考にして、switch文がある行まで実行行を移動させてください。

バグが発生する状況を作り出す

上の実行結果から見るに、

  1. userHand=0,computerHand=0のとき
  2. userHand=1,computerHand=1のとき

にバグが発生しているようです。

userHandとcomputerHandに値を設定して、バグが発生する状況を作り出しましょう。
設定したい変数にマウスを当て変数の値をダブルクリックし入力すると、変数の値をデバッグ実行中に変更することが出来ます。
alt

上の1か2の状況を作ってください。

バグがありそうな所まで実行行を移す

対戦結果にバグがあるため、対戦結果を判定するjudgeUserWinLoseDraw関数にバグがありそうだと推測することが出来ます。

予備知識で紹介したステップイン・ステップアウトを使って、judgeUserWinLoseDraw関数の内部まで実行行を移動させてください。

すると1の状況の場合は下の画像、2の状況の場合はさらに下の画像に実行行が移るはずです。
alt
alt

matchResultは呼び出し元に対戦結果の値を返す変数です。1,2の状況はどちらも「引き分け」ですから、「引き分け」を示す値DRAWmatchResultに格納されていなければなりません。

しかし、ソースコードを見るとどちらもWINが代入されています。これが原因で最初に挙げたようなバグが発生していたようです。

後はWINDRAWに修正すればデバッグは完了です。

次回予告

次回は、VisualStudioでダイナミックリンクライブラリ(DLL)を作成する方法を解説します。

次回の記事

terapotan.hatenablog.jp

前回の記事

terapotan.hatenablog.jp

連載記事一覧

terapotan.hatenablog.jp