Nodachisoft Nodachi Sword Icon
  
@あまじ✎ 2021年3月12日に更新

第2章60 ファイルの読込

イチからゲーム作りで覚えるC言語
第2章59 ファイルへの書き込み : PREV
NEXT : 第2章61 文字列で日本語(マルチバイト)を扱う :

概要

C言語でハードディスクなどのファイル上にあるデータを読み込む方法を確認していきます。

通常、後から変更を加えたり、自由に編集したいデータはプログラムの中に定数で書くのではなく、 ファイル等で読み込むようにしておくと、プログラム本体を変更しなくともデータを編集すれば あとからゲームバランスの変更がしやすくなるというメリットがあります。

テキストファイルからの読み込み

適当なテキストファイルをゲームのセーブデータと仮定して、中身を読み込んでみましょう。

まずはシンプルに、1文字づつファイルポインタのストリームから読み込んで、ファイルの内容をすべて表示することを考えます。

 
readtextbuf.c
#include <stdio.h>
int main () {
  FILE *fp = fopen("save1.txt", "r");
  char c;
  do {
        c = fgetc(fp);
        if( feof(fp) ) {
          break ;
        }
        printf("%c", c);
  } while(1);
  fclose(fp);
}

同じフォルダに「save1.txt」ファイルが無ければ作ってみてください。 私は下のようにメモ帳などで作成しました。

 
save1.txt
name=ぷらんく
level=1
hp=10

プログラムを実行すると、下のような結果となりました。

readtextbuf.c結果
name=ぷらんく
level=1
hp=10

プログラムから save1.txt のファイルの中身を読み取り、すべて表示できていますね!

fgetc関数

6 行目で fgetc 関数が登場しています。

 
readtextbuf.c
c = fgetc(fp);

ファイルストリームから、一文字(1つのcharぶん)を読み込む関数です。 fgetc で 1 char ぶんを正しく読み込めたら、ファイルストリーム(変数 fp)で読み込んでいるファイルの読み込み位置は一つ後ろにずれます。 読み込んだ 1文字は return で返ってきて、変数 c に代入されています。

feof関数

7行目でも新しく feof 関数が登場しています。

 
readtextbuf.c
if( feof(fp) ) {

feof(FILEポインタ) で呼び出すことができ、引数で渡したFILEポインタの読み込み位置が ファイルの終わりに来ているかを確認してくれます。

もしFILEポインタがファイルの末尾まで来ている場合、feof は 0 以外を返し、 まだFILEポインタがファイルの途中を読み込んでいる時は、feof は 0 を返します。

6 行目は、ファイルの終わりまで読み込んだ時に、true となり、if文の中が実行されることになります。

このプログラムでは、ファイルの終わりまで読み込んだら、 break 文により、whileの繰り返しから 抜け出すことができます。

読み込むファイルのサイズチェック

ファイルの中身を一度、プログラム内でバッファとして全て読み込んでから、 中身を処理していくようなケースはよくあります。

その場合、読み込もうとしているファイルサイズが いったいどのくらいのデータ量なのかを前もって確認したほうが安全です。

C言語の標準仕様では、対象のファイルサイズを確認するための関数はないため、 今回は、指定したファイルのサイズを確認するオリジナル関数、getfilesize を作成します。

 
getfilesize.c
#include <stdio.h>

int getfilesize( const char *filename) {
  int filesize;
  FILE* fp = fopen(filename, "rb");
  if ( fp == NULL ) {
    return -1;
  }
  fseek(fp, 0, SEEK_END);
  filesize = ftell(fp);
  fclose(fp);
  return filesize;
}

int main () {
  int filesize = getfilesize("save1.txt");
  printf("サイズは %d バイトでした!\n", filesize);
}

適当に、同じフォルダに「save1.txt」というファイルを作成してから このプログラムを実行すると、下のような結果になります。

getfilesize.c結果
サイズは 35 バイトでした!

getfilesize 関数の仕様

  • 第一引数 filename には、サイズを確認したいファイル名を指定します。
  • filename のファイルサイズを確認し、バイト数を返します。
  • もし filenmae で指定したファイルが存在しないときは -1 が返ってきます。

コードの中身確認

コードの中身を確認していきます。 getfilesize 関数の中身を見てみます。

5行目で、fopen 関数を使い、変数 filename の文字列のファイルへアクセスをしています。

 
getfilesize.c
  FILE* fp = fopen(filename, "rb");

モードは "rb" ですので、r:読み込み、b:バイナリ で、「バイナリファイルとして読み込みするよう、ファイルを開く」という 指定をしています。結果として、ファイルへのポインタ(ストリーム)は変数 fp に代入されます。

なにかしら上手くファイルを開けなかった場合、fopen関数は NULL を返しますので、 6~8 行目で条件分岐し、 -1 を返すようにします。

 
getfilesize.c
  if ( fp == NULL ) {
    return -1;
  }

続いて 9行目~10行目で fseek 関数ftell 関数が登場します。

 
getfilesize.c
  fseek(fp, 0, SEEK_END);
  filesize = ftell(fp);

fseek は開いたファイルポインタのストリーム上での読み取り位置を変更できます。

下のような関数で定義されています。

fseek仕様
int fseek( FILEポインタ, 開始オフセット, 起点);

ファイルの終わり(よく End of file の頭文字を問って {imp]EOF と呼びます)へ、 読み取り位置をずらすには、第三引数に、定数である「SEEK_END」を指定します。

※この SEEK_END は stdio.h ヘッダーの中で定義されています。

次に、ftell 関数で現在のファイルポインタのストリーム上での読み取り位置を整数で返します。

fseek 関数により、ファイルの末尾にファイルの読み取り位置が移動されているため、 ファイルの末尾の位置が得られることになります。

ファイルのサイズは、ファイルの末尾の読み取り位置と一致するので、 これで無事に、ファイルのサイズを得ることができました!

テキストファイルを一気に読み込み

ゲームデータやシナリオデータなど、読み込むテキストファイルのサイズが大きい場合、 malloc 関数等を使って、ヒープ領域に確保したメモリに読み込むことが必要です。 (通常の main 関数内で確保した char buf[サイズ] では、確保できるメモリ量に限界があるためです)

先ほど作成した getfilesize 関数で得られたファイルのサイズに合わせて、 malloc 関数でメモリを確保して、そこにテキストファイルを読み込んでから表示してみましょう!

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

int getfilesize( const char *filename) {
  int filesize;
  FILE* fp = fopen(filename, "rb");
  if ( fp == NULL ) {
    return -1;
  }
  fseek(fp, 0, SEEK_END);
  filesize = ftell(fp);
  fclose(fp);
  return filesize;
}

char* readtextfile( const char *filename ) {
  int filesize = getfilesize( filename );
  char *data = NULL;
  if ( filesize == -1 ) {
    return NULL;
  }
  data = (char*) malloc( filesize + 1);
  FILE *fp = fopen(filename, "r");
  if ( fp == NULL ) {
    return "";
  }
  fread( data, 1, filesize, fp);
  data[filesize] = '\0';
  return data;
}

int main () {
  char *readbuffer = readtextfile("save1.txt");
  printf("==ファイルの中身・ここから==\n");
  printf("%s\n",readbuffer);
  printf("==ファイルの中身・ここまで==\n");
  free(readbuffer);
}

実行すると下のような結果となります。

結果
==ファイルの中身・ここから==
name=ぷらんく
level=1
hp=10
==ファイルの中身・ここまで==

新しく readtextfile という自作関数を用意しました。 この関数の引数にファイルを渡すと、読み取るファイルのサイズに併せて ヒープ領域にメモリを確保して、ファイルから読み込んでくれます。

読み込んだテキストファイルの中身の先頭ポインタを return で返してくれます。

コードの中身

readtextfile 関数のコードの中身について、新しく登場した fread 関数 について確認していきましょう! fread 関数でファイルの中身を指定したサイズ分読み込む処理をしています。

 
readtextfile.c
fread( data, 1, filesize, fp);

fread 関数の使い方は下の通りです。

fread( 読み込む先のポインタ, 読み込む1要素のサイズ(バイト), 要素の数, 読み込む元のFILEポインタ );

英数字であれば、1文字は 1 バイトですので、読み込む 1 要素のサイズには 1 (バイト)を指定しています。 また、文字列として filesize 文字数ぶんを読み込むので要素の数に filesize を指定します。

これで第三引数に指定した FILEポインタから 1 × filesize ぶんのサイズを、ポインタ変数 data の中に 読み込んでくれます。

fread でテキストファイルを全て読み込んだ後に、以下の処理をしています。

 
readtextfile.c
data[filesize+1] = '\0';

これで、文字列としての終わりを定義しています。 作成したファイルの最後に「'\0'」(終端文字)がないため、自分で追加してあげています。

これをしないと、printf で変数 data を表示しようとしたときに、'\0' が存在せず、 文字列としての終わりがわからないため、おかしな表示をしてしまう可能性があります。

この終端文字を最後に入れておきたかったので、メモリを malloc 関数で確保する時に、欲しいサイズとして 1 バイトぶんプラスしています。

 
readtextfile.c
data = (char*) malloc( filesize + 1);

バイナリデータを読み込む

下は参考として、バイナリデータを読み込むための関数例です。 ※ファイルが存在しないときのエラーチェックは省略してます。

 
readBinaryFile.c
// 指定したファイルのファイルサイズを確認して return で返す
int getFileSize(const char *fileName) {
    FILE* fp = fopen(fileName , "rb");
    long fileSize = 0;
    fseek(fp, 0, SEEK_END);
    fileSize = ftell(fp);
    rewind(fp);
    fclose(fp);
    return  fileSize;
}

// バイナリデータをヒープ領域のメモリに読み込む
char* readBinaryFile(const char* fileName) {
    int fileSize = getFileSize(fileName);
    char* fileData = (char*)calloc(sizeof(char), fileSize + 1);
    FILE* fp = fopen(fileName, "rb");
    int resultSize = fread(fileData, sizeof(char), fileSize, fp);
    fclose(fp);
    if (resultSize != fileSize) {
        printf("システム:正しくファイルが読み込めませんでした。サイズが一致しません。\n");
        exit(-1);
    }
    return fileData;
}

readBinaryFile 関数で引数に読み込みたいバイナリファイルを指定すると、 全てメモリのヒープ領域に読込、ポインタを返してくれます。

参考文献

(英語) C11仕様 - N1570 - 7.21.9.2 The fseek function - https://port70.net/~nsz/c/c11/n1570.html#7.21.9.2

イチからゲーム作りで覚えるC言語
第2章59 ファイルへの書き込み : PREV
NEXT : 第2章61 文字列で日本語(マルチバイト)を扱う :
 
 
送信しました!

コメント、ありがとうございます。

なんかエラーでした

ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。

Twitter:@NodachiSoft_jp
お名前:
 
連絡先:
 
メッセージ:
 
戻る
内容の確認!

以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください

お名前:
 
連絡先:
 
メッセージ:
 
Roboto からの操作ではないという確認のため確認キーを入れてください。
確認キー=95
戻る
 / 
送信確認へ
コメント欄
コメント送信確認へ

関連ありそうな記事(5件)です!

第1章01 Visual Studio Community 2019 のインストール手順

#C11仕様#C言語#ゲームプログラミング✎ 2021-08-08
C言語でプログラミングをするために、無料で使える Visual Studio Community を使った開発環境を揃えていく手順や注意点をお話しています。
目次
第2章60 ファイルの読込
第2章60 ファイルの読込
概要
概要
テキストファイルからの読み込み
テキストファイルからの読み込み
fgetc関数
fgetc関数
feof関数
feof関数
読み込むファイルのサイズチェック
読み込むファイルのサイズチェック
getfilesize 関数の仕様
getfilesize 関数の仕様
コードの中身確認
コードの中身確認
テキストファイルを一気に読み込み
テキストファイルを一気に読み込み
コードの中身
コードの中身
バイナリデータを読み込む
バイナリデータを読み込む
参考文献
参考文献
Nodachisoft © 2021