★ポインタ(メモリ領域の確保と解放3)★


malloc関数で配列を確保して使うサンプルプログラムを作ります。

普通に配列を用意するのではなく、malloc関数で配列を確保するということは、次のような状況が考えられます。

  プログラムの実行前にデータの個数が分からない

 毎回データの個数が変わる

今回は、この状況を踏まえてサンプルを作っていきます。


ファイルからあるテキストデータを読み込んで表示することになりました。

  データの個数については追加や削除が行われる可能性があり、不定の状態です。

  現在のデータの個数については、ファイルの先頭に格納されています。

この状況を踏まえて、プログラムの大まかな流れを考えてみましょう。

1.ファイルを開く

2.ファイルからデータの個数を読み込む

3.malloc関数を使って、個数分の配列を用意する

4.データを個数分読み込む

5.ファイルを閉じる

6.データを表示する

7.free関数を使って、領域を解放する

大まかにはこれで行けそうです。

気を付けるべき点もいくつかありますので、少しずつ作っていきましょう。

※ファイル分割はせず、main関数のみで作成します。


まずプロジェクトを作成し、今回使用するデータファイルをダウンロードしてください。

 Monster.txt

※リンクを右クリックして「対象をファイルに保存」を選択し、プロジェクトのフォルダに入れる。

ファイルの中を見てみると、

40
スライム          5   3   3   1
アシッドスライム  7   3   4   1
バット            9   6   6   2
ゴースト         11   8   7   3
・
・
・

とデータが入っています。

最初の40が現在のデータ個数です。

次の行からデータが続いており、1行が1つのレコード(データの集まり)になっています。

データはタブ文字で区切られており、fscanf関数で読み込めるようになっています。

データの内訳は、左から

名 前 最大半角20文字分
攻撃力 整数
防御力 整数
最大HP 整数
経験値 整数

となります。

Monster構造体を用意し、データを格納できるようにしましょう。


<sample program 168-01>

#include <stdio.h>
#include <stdlib.h>

#define NAME_MAX 21

typedef struct {
    char name[NAME_MAX];
    int attack;
    int defence;
    int hp;
    int experience;
} Monster;

int main(void)
{
   /* 1.ファイルを開く */

  /* 2.ファイルからデータの個数を読み込む */

  /* 3.malloc関数を使って、個数分の配列を用意する */

  /* 4.データを個数分読み込む */

  /* 5.ファイルを閉じる */

  /* 6.データを表示する */

  /* 7.free関数を使って、領域を解放する */

    return 0;
}

今回は完成するまで実行出来ない予定です・・・


次にファイルを開くとファイルを閉じるを追加します。

<sample program 168-02>

#include <stdio.h>
#include <stdlib.h>

#define NAME_MAX 21

typedef struct {
    char name[NAME_MAX];
    int attack;
    int defence;
    int hp;
    int experience;
} Monster;

int main(void)
{
    FILE *fpMonster;

   /* 1.ファイルを開く */

    fpMonster = fopen("Monster.txt", "r");

    if (!fpMonster) {
        return 1;
    }

  /* 2.ファイルからデータの個数を読み込む */

  /* 3.malloc関数を使って、個数分の配列を用意する */

  /* 4.データを個数分読み込む */

  /* 5.ファイルを閉じる */

    fclose(fpMonster);

    fpMonster = NULL;

  /* 6.データを表示する */

  /* 7.free関数を使って、領域を解放する */

    return 0;
}

ファイルポインタ fpMonster を用意し、読み込みモードで開きました。

ファイルポインタもポインタですから、fclose関数で閉じた後は、ダングリングポインタを防ぐために NULL を入れています。

ファイルを開くと同時に閉じるプログラムを作っておくと誤って実行してしまった時にも安心ですね。


次はデータの個数を読み込む箇所を作りましょう。

<sample program 168-03>

#include <stdio.h>
#include <stdlib.h>

#define NAME_MAX 21

typedef struct {
    char name[NAME_MAX];
    int attack;
    int defence;
    int hp;
    int experience;
} Monster;

int main(void)
{
    FILE *fpMonster;

    int dataCount;

   /* 1.ファイルを開く */

    fpMonster = fopen("Monster.txt", "r");

    if (!fpMonster) {
        return 1;
    }

  /* 2.ファイルからデータの個数を読み込む */

    fscanf(fpMonster, "%d", &dataCount);

    if (dataCount <= 0) {
        fclose(fpMonster);
        return 0;
    }

  /* 3.malloc関数を使って、個数分の配列を用意する */

  /* 4.データを個数分読み込む */

  /* 5.ファイルを閉じる */

    fclose(fpMonster);

    fpMonster = NULL;

  /* 6.データを表示する */

  /* 7.free関数を使って、領域を解放する */

    return 0;
}

個数を読み込んだ後で、

if (dataCount <= 0) {
    fclose(fpMonster);
    return 0;
}

を実行しています。

個数が0であれば、データを読み込む必要も、表示する必要もありません。

データが無いのですから正常終了である「0」を返します。

ただ、このまま終わるとファイルは開きっぱなしになりますので、fcloseでファイルを閉じています。

細かいところまで考えながら作ってください。


次は、メモリの確保と解放を追加します。

<sample program 168-04>

#include <stdio.h>
#include <stdlib.h>

#define NAME_MAX 21

typedef struct {
    char name[NAME_MAX];
    int attack;
    int defence;
    int hp;
    int experience;
} Monster;

int main(void)
{
    FILE *fpMonster;

    int dataCount;

    Monster *pMonsterList;

   /* 1.ファイルを開く */

    fpMonster = fopen("Monster.txt", "r");

    if (!fpMonster) {
        return 1;
    }

  /* 2.ファイルからデータの個数を読み込む */

    fscanf(fpMonster, "%d", &dataCount);

    if (dataCount <= 0) {
        fclose(fpMonster);
        return 0;
    }

  /* 3.malloc関数を使って、個数分の配列を用意する */

    pMonsterList = (Monster*)malloc(sizeof(Monster) * dataCount);

    if (!pMonsterList) {
        fclose(fpMonster);
        return 1;
    }

  /* 4.データを個数分読み込む */

  /* 5.ファイルを閉じる */

    fclose(fpMonster);

    fpMonster = NULL;

  /* 6.データを表示する */

  /* 7.free関数を使って、領域を解放する */

    free(pMonsterList);

    pMonsterList = NULL;

    return 0;
}

ポインタ変数pMonsterListを用意して、データの個数分 Monster構造体を配列として確保しました。

確保できなければプログラムを終了させますので、異常終了「1」を返して終わります。

このまま終わるとファイルが開きっぱなしになるので、fclose関数を呼び出しています。

また、free関数(+ダングリングポインタ対策)を先に作っておけば、誤って実行した時でもメモリリークが発生しなくなります。


では、残りのデータ読み込みと表示を追加しましょう。

<sample program 168-05>

#include <stdio.h>
#include <stdlib.h>

#define NAME_MAX 21

typedef struct {
    char name[NAME_MAX];
    int attack;
    int defence;
    int hp;
    int experience;
} Monster;

int main(void)
{
    FILE *fpMonster;

    int dataCount;

    Monster *pMonsterList;

    int i;

   /* 1.ファイルを開く */

    fpMonster = fopen("Monster.txt", "r");

    if (!fpMonster) {
        return 1;
    }

  /* 2.ファイルからデータの個数を読み込む */

    fscanf(fpMonster, "%d", &dataCount);

    if (dataCount <= 0) {
        fclose(fpMonster);
        return 0;
    }

  /* 3.malloc関数を使って、個数分の配列を用意する */

    pMonsterList = (Monster*)malloc(sizeof(Monster) * dataCount);

    if (!pMonsterList) {
        fclose(fpMonster);
        return 1;
    }

  /* 4.データを個数分読み込む */

    for (i = 0; i < dataCount; i++) {
        fscanf(fpMonster, "%s", pMonsterList[i].name);
        fscanf(fpMonster, "%d", &pMonsterList[i].attack);
        fscanf(fpMonster, "%d", &pMonsterList[i].defence);
        fscanf(fpMonster, "%d", &pMonsterList[i].hp);
        fscanf(fpMonster, "%d", &pMonsterList[i].experience);
    }

  /* 5.ファイルを閉じる */

    fclose(fpMonster);

    fpMonster = NULL;

  /* 6.データを表示する */

    for (i = 0; i < dataCount; i++) {
        printf("%20s |", pMonsterList[i].name);
        printf("%4d|", pMonsterList[i].attack);
        printf("%4d|", pMonsterList[i].defence);
        printf("%4d|", pMonsterList[i].hp);
        printf("%4d\n", pMonsterList[i].experience);
    }

  /* 7.free関数を使って、領域を解放する */

    free(pMonsterList);

    pMonsterList = NULL;

    return 0;
}

<実行結果>

            スライム|   5|   3|   3|   1
    アシッドスライム|   7|   3|   4|   1
              バット|   9|   6|   6|   2
            ゴースト|  11|   8|   7|   3
              メイジ|  11|  12|  13|   4
          ソーサラー|  14|  14|  15|   5
        スコーピオン|  18|  16|  20|   6
            グロッグ|  20|  18|  22|   7
        ハイゴースト|  18|  20|  23|   8
            トロール|  24|  24|  25|  10
        ホブゴブリン|  22|  26|  20|  11
          スケルトン|  28|  22|  30|  11
          マジシャン|  28|  22|  30|  13
アイアンスコーピオン|  36|  42|  22|  14
    ライカンスロープ|  40|  30|  34|  16
            シャドウ|  44|  34|  36|  17
        メタルバット|  10| 255|   4| 115
        へルゴースト|  40|  38|  36|  18
        ハイトロール|  50|  36|  38|  20
        オーガロード|  47|  40|  35|  20
        オーガメイジ|  52|  50|  38|  22
            キマイラ|  56|  48|  42|  24
    デススコーピオン|  60|  90|  35|  26
      ゴーストナイト|  68|  56|  46|  28
            ゴーレム| 120|  60|  70|   5
    ゴールドゴーレム|  48|  40|  50|   6
      アーマーナイト|  76|  78|  55|  33
      キマイラメイジ|  78|  68|  58|  34
      シャドウナイト|  79|  64|  50|  37
          フェンリル|  86|  70|  60|  40
            ドラゴン|  88|  74|  65|  45
          アマリリス|  86|  80|  65|  43
        アークロード|  80|  70|  65|  50
        デモンナイト|  94|  82|  70|  54
        デスドラゴン|  98|  84|  70|  60
    ストーンリザード| 100|  40| 160|  65
          デスナイト| 105|  86|  90|  70
      ダークドラゴン| 120|  90| 100| 100
    クィーンドラゴン|  90|  75| 100|   0
      キングドラゴン| 140| 200| 130|   0
続行するには何かキーを押してください・・・

完成しました!

名前の欄は、

printf("%20s |", pMonsterList[i].name);

としていますので、20桁で表示しますから、先頭に空白が出来ています。

↓のように、

printf("%-20s |", pMonsterList[i].name);

桁数の前に「−」を付けることで「左寄せ」表示が出来ます。

<実行結果>

スライム            |   5|   3|   3|   1
アシッドスライム    |   7|   3|   4|   1
バット              |   9|   6|   6|   2
ゴースト            |  11|   8|   7|   3
メイジ              |  11|  12|  13|   4
ソーサラー          |  14|  14|  15|   5
スコーピオン        |  18|  16|  20|   6
グロッグ            |  20|  18|  22|   7
ハイゴースト        |  18|  20|  23|   8
トロール            |  24|  24|  25|  10
ホブゴブリン        |  22|  26|  20|  11
スケルトン          |  28|  22|  30|  11
マジシャン          |  28|  22|  30|  13
アイアンスコーピオン|  36|  42|  22|  14
ライカンスロープ    |  40|  30|  34|  16
シャドウ            |  44|  34|  36|  17
メタルバット        |  10| 255|   4| 115
へルゴースト        |  40|  38|  36|  18
ハイトロール        |  50|  36|  38|  20
オーガロード        |  47|  40|  35|  20
オーガメイジ        |  52|  50|  38|  22
キマイラ            |  56|  48|  42|  24
デススコーピオン    |  60|  90|  35|  26
ゴーストナイト      |  68|  56|  46|  28
ゴーレム            | 120|  60|  70|   5
ゴールドゴーレム    |  48|  40|  50|   6
アーマーナイト      |  76|  78|  55|  33
キマイラメイジ      |  78|  68|  58|  34
シャドウナイト      |  79|  64|  50|  37
フェンリル          |  86|  70|  60|  40
ドラゴン            |  88|  74|  65|  45
アマリリス          |  86|  80|  65|  43
アークロード        |  80|  70|  65|  50
デモンナイト        |  94|  82|  70|  54
デスドラゴン        |  98|  84|  70|  60
ストーンリザード    | 100|  40| 160|  65
デスナイト          | 105|  86|  90|  70
ダークドラゴン      | 120|  90| 100| 100
クィーンドラゴン    |  90|  75| 100|   0
キングドラゴン      | 140| 200| 130|   0
続行するには何かキーを押してください・・・

データを追加したり、減らしたりして試してみてください。

※個数も変えるのを忘れずに!


次へ

戻る

目次へ