ファイルの種類

テキストファイルとバイナリファイル

ここでは、C言語によるファイルの読み書きについて学ぶこととしましょう。その前に、まずはファイルの種類について簡単に説明しましょう。以下の表を見てください(表6-1)。この表から見てもわかる通り、ファイルには大きくわけて、テキストファイルバイナリファイルが存在します。C言語でこれらを扱う方法は、基本的な部分に変わりはありませんが、ここではよりわかりやすく、汎用性の高いテキストファイルから説明することにします。

表6-1.ファイルの種類
ファイルの種類内容
テキストファイル文字として読めるデータ。.txtファイル、.cファイル、.htmlファイルほか
バイナリファイル文字として読めないデータ。(画像や音声など).pngファイル、.wavファイルほか

テキストファイルの書き込み

サンプルプログラム

ファイル入出力の手始めとして、まずは最も基本的な処理であるテキストファイルの出力のサンプルをみてみましょう。以下のプログラムを入力し、 実行する前に、Cドライブ直下に"test"フォルダを作成してみてください。

listex6-1:main.c
#include <stdio.h>
#include <stdlib.h>

void main() {
	FILE *file;										// ファイルポインタ(出力用)
	file = fopen("c:¥¥test¥¥sample.txt", "w"); 		// ファイルを書き込み用にオープン(開く)
	if (file == NULL) {			 					// オープンに失敗した場合
		printf("ファイルが開けません。¥n");			// エラーメッセージを出して
		exit(1);									// 異常終了
	}
	//	ファイルにデータを書き込む
	fprintf(file,"Hello World.¥r¥n");		// ファイルに書く
	fprintf(file,"ABCDEF¥r¥n");
	fclose(file);         				// ファイルをクローズ(閉じる)
}

プログラムを実行しても、画面上に特に大きな変化は現れません。しかし、testフォルダを開くと、「sample.txt」というファイルが存在し、中を見ると

sample.txt
Hello World.
ABCDEF

と記述されているはずです。

では、順を追って説明していきましょう。まず、2行目で、ファイルのポインタを宣言しています。これは、ファイルを操作するときには必ず必要になるポインタです。

ファイルのオープン

次に、3行目でファイルをオープンしています。通常、ファイル処理を行うときは、対象となるファイルを「オープン」する処理が必要となります。 ファイルのオープンは、fopen関数を用いますが、書式は、以下の通りです。

表6-3.ファイルオープンの関数
関数書式意味使用例
fopen()fopen(ファイル名,モード);ファイルを、指定したモードで開く。
戻り値が、ファイルのポインタ。
FILE* fp = open("sample.txt","w");

このプログラムの場合、c:¥testフォルダ内にある、sample.txtというファイルを開くことになります。モードでで出ている"w"は、書き込みを意味します。なお、fopenのモードには、以下のものがあります。(表6-2)

表6-2.fopen()関数のモード
モード機能ファイルが存在しないとき
rテキストデータの読み込みエラーになる
wテキストデータの書き込みファイルを新規作成する
aテキストデータへの追加書き込みファイルを新規作成する
r+テキストデータを更新モードで開く(書き込み・読み込み共に可)エラーになる
w+テキストデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する
a+テキストデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する
rbバイナリデータの読み込みエラーになる
wbバイナリデータの書き込みファイルを新規作成する
abバイナリデータへの追加書き込みファイルを新規作成する
rb+ / r+bバイナリデータを更新モードで開く(書き込み・読み込み共に可)エラーになる
wb+ / w+bバイナリデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する
ab+ / a+bバイナリデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する

この表から判る通り、ここでは、テキストファイルを書き込みモードで開いていることが判ります。このとき、ファイルのオープンが失敗すると、 戻り値にNULLが返されます。そのときは、異常終了の処理に入ります。

また、ファイルのオープンに成功すると、fileにファイルポインタが渡されます。今後、開かれたファイルに対するアクセス(書き込み・読み込みなど) をするには、このポインタが必要になります。

ファイルへの書き込み

このプログラムは、ファイルを書き込みのプログラムなので、次はファイルの書き込み処理となります。まず、書き込み処理の関数を見てみましょう。(表6-3)

表6-3.ファイルへの書き込み処理関数
関数書式意味使用例
fprintf()fprintf(ファイルポインタ,書き込み文字列,変数・・・);文字列をファイルに書き込むfprintf(fp,"no=%d",i);

見てわかるとおり、fprintf()は、画面に文字を出力するprintf()関数と似ています。なお、OSがウィンドウズの場合、改行コードは「¥n」ではなく、「¥r¥n」と2文字 にります。改行を「¥n」とすると、ファイルが改行されない可能性があるので気をつけましょう。linux系のOSでは、「¥n」に書き変えなくてはなりません。

ファイルのクローズ

ファイル書き込みの一連の処理が終わったら、ファイルを閉じますファイルをとじるのはclose(クローズ)関数です。書式は以下の通りです。(表6-4)

表6-4.ファイルをクローズする関数
関数書式意味使用例
fclose()fclose(ファイルポインタ);指定したファイルポインタのファイルを閉じるfclose(fp);

これで、ファイルをオープンしてから、書き込み、クローズまでの一連の処理がわかりました。次は、書き込んだファイルを読み込んでみましょう。

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

サンプルプログラム

次は、listex6-1で書き込んだファイルを読み込み、画面に表示するプログラムを作って見ましょう。以下のサンプルを実行してみてください。

listex6-2:main.c
#include <stdio.h>
#include <stdlib.h>

#define SIZE	256

void main() {
	FILE *file;									// ファイルポインタ(読み込み用)
	char line[SIZE];							//	読み込む行
	line[0] = '¥0';								//	初期化(空文字列)
	file = fopen("c:¥¥test¥¥sample.txt", "r"); 	// ファイルを読み込み用にオープン(開く)
	if (file == NULL) {							// オープンに失敗した場合
		printf("ファイルが開けません。¥n");		// エラーメッセージを出して
		exit(1);                         		// 異常終了
	}
	//	ファイルのデータ読み込む
	while ( fgets(line, SIZE, file) != NULL ) {
		printf("%s", line);
	}
	fclose(file);          // ファイルをクローズ(閉じる)
}
実行結果①(入力した値が同じ場合)
Hello World.
ABCDEF

実行結果から判る通り、このプログラムによって、さきほど書き込まれたsample.txtが読み込まれ、その内容が表示されているのがわかります。 ファイル読み込み時も、ファイルをオープンし、クローズするという一連の流れは変わりません。違いは、オープン時にモードが"r"になっていることぐらいです。

1行ごとのファイルの読み込み

また、ファイルからデータを読み出す関数は複数ありますが、ここでは特に、テキストデータということで、テキストデータを一行読み出すfgets()関数を用いました。 以下に、関数の説明を書きます。(表6-5)

表6-5.fgets()関数
関数書式意味使用例
fgets()fgets(文字列,文字列サイズ,ファイルポインタ);指定したサイズの文字列を
ファイルから読み込む。
fgets(s,10,fp);

ここでいう文字列は、最初の引数として指定した文字列のサイズのことです。読み込みが成功すれば、これと同じ値が返ってきます。読み込みがうまくいかなかったり、最後まで読みきった場合は、 NULLが帰ってきますので、このプログラムでは、NULLが帰ってくるまで読み込みを行い、表示するという処理をwhileループで行っています。

このプログラムで、文字列を入れる配列lineは、256という長さをとっています。気をつけなくてはならないのは、実際の文字列で一行の最大の文字列がこの値を超えてしまう場合もあるということです。 なので、この配列のサイズは長めに取っておくことをおすすめします。このプログラムでも、実際はせいぜい10文字前後の文字列を読み込むのに、不必要なまでに大きなサイズの文字列を取っているのはそのためです。

1文字ごとのファイルの読み込み

しかし、それでもファイルの一行のサイズが、用意された文字列のファイルのサイズを超えてしまうことも想定されます。そこで、次に、1行ごとではなく、1文字ずつファイルを読み込み、表示する方法を紹介しましょう。

listex6-3:main.c
#include <stdio.h>
#include <stdlib.h>

void main() {
	FILE *file;                                   // ファイルポインタ(読み込み用)
	int c;                                        //  読み込む文字のコード
	file = fopen("c:¥¥test¥¥sample.txt", "r");    // ファイルを読み込み用にオープン(開く)
	if (file == NULL) {                            // オープンに失敗した場合
		printf("ファイルが開けません。¥n");                // エラーメッセージを出して
		exit(1);                                            // 異常終了
	}
	//    ファイルのデータ読み込む
	while ( (c=fgetc(file)) != EOF){
		printf("%c",(char)c);
	}
	fclose(file);          // ファイルをクローズ(閉じる)
}

実行結果は、listex6-2と同じなので省略します。このプログラムでは、fgetc()関数を用いて一字ずつ文字を取得し、それを表示しています。cには、文字コードが入りますので、 printf()関数では、文字列ではなく、文字の表示を表す'%c'が用いられています。fgetc()を実行すると、先頭から順次ファイルを読み込んでいきます。戻り値が文字コードとなって いますが、ファイルの最後まで到達すると、EOFが返ってきます。したがって、このプログラムでは、EOFが出現するまでファイルを読み続け、表示することにより、読み込んだファイル が全て表示される仕組みになっています。

fgetc()とfgets()の違いのイメージは、以下のようになります。(図6-1)

図6-1.fgetc()とfgets()の違い
fgetc()とfgets()の違い

なお、fgetc()関数の概要は以下の通りです。(表6-6)

続いては、その他よく使う数学関数を見てみましょう。

表6-6.fgetc()関数
関数書式意味使用例
fgetc()fgetc(ファイルポインタ);指定したテキストファイルか
一文字読み込む。戻り値が文字コード。EOFが終了。
c = fgetc(fp);

バイナリファイルの読み書き

サンプルプログラム

続いて、バイナリファイルの書き込みと読み込みを行うプログラムを作ってみましょう。

listex6-4:main.c
#include <stdio.h>
#include <stdlib.h>

void main() {
	FILE* file;
	int i;
	//	書き込むデータ
	char wdata[] = { 0x10 , 0x1a , 0x1e , 0x1f };
	char rdata[4];
	//バイナリデータの書き込み
	file = fopen( "C:¥¥test¥¥test.bin", "wb" );
	if( file == NULL ){
		printf( "ファイルオープンに失敗しました。\n" );
		exit( 1 );
	}
	fwrite( wdata, sizeof(char), sizeof(wdata), file );
	fclose(file);          // ファイルをクローズ(閉じる)
	//バイナリデータの書き込み
	file = fopen( "C:¥¥test¥¥test.bin", "rb" );
	if( file == NULL ){
		printf( "ファイルオープンに失敗しました。\n" );
		exit( 1 );
	}
	fread( rdata, sizeof(char), sizeof(rdata), file );
	fclose(file);          // ファイルをクローズ(閉じる)
	//	結果を表示
	for(i = 0; i < sizeof(rdata) ; i++){
		printf("%x ",rdata[i]);
	}
	printf("¥n");
}

実行結果
10 1a 1e 1f

処理の流れ

10行目から17行目は、ファイルへデータを書き込んでいます。fopen()でファイルを開くのは同じですが、モードを"wb"に指定していることから、 バイナリモードでの書き込みとなります。ここでは、fwrite()でデータを書き込みます。ファイル名は、cドライブ直下のtestフォルダー内にあるtest.binに書き込まれます。

データは、char型の配列wdataのデータを書き込んでいます。最後は、fclose()でファイルを閉じて、書き込みが終了します。

次に、18行目から25行目でデータを読み込みます。fopen()でファイルを開きますが、今度はバイナリファイルの読み込みなので、モードに"rb"を指定します。バイナリモードでの読み込みは、fread()関数を用います。ここでは、char型の配列rdataに値を読み込んでいます。最後はclose()でファイルを閉じて終了です。

fwrite,fread

最後に、rdataの値を16進数で表示すると、書き込んだ通りのデータが読み込まれていることがわかります。 ここで出てきたfwrite()および、fread()関数に関する説明は、以下の通りです。(表6-7)

表6-7.バイナリデータの読み書きに用いる関数
関数書式意味使用例
fwrite()fwrite(データ,データのバイト長,
データの数,ファイルポインタ);
バイナリデータ
の書き込み。
fwrite(data,sizeof(int),sizeof(data),fp);
fread()fread(データ,データのバイト長,
データの数,ファイルポインタ);
バイナリデータ
の読み込み。
fread(data,sizeof(int),sizeof(data),fp);

16行目のfwrite()関数では、wdataのデータを書き込んでいます。wdataは、char型のサイズ4の配列なので、データのバイト数に、char型のサイズのバイト数を表すsizeof(char)、 データの数に、配列wdataの大きさを指すsizeof(wdata)を指定します。

24行目のfread()関数では、rdataのデータを書き込んでいます。rdataは、fwriteの場合と同様に、char型のサイズ4の配列なので、データのバイト数に、char型のサイズのバイト数を表すsizeof(char)、 データの数に、配列rdataの大きさを指すsizeof(rdata)を指定します。

大きさのわからないファイルの読み込み

ファイル読出しの問題点

listex6-4で、バイナリファイルの基本的な読み書きの説明をしました。しかし、ここには大きな問題点があります。バイナリファイルに限った事ではありませんが、ファイルは 書き込むときは比較的簡単ですが、読み込むときには大きな問題があります。それは、読み込むべきファイルの大きさが分からないということです。

サンプルプログラム

おそらく、ほとんどのプログラムは、サイズのわからないデータを読み込まなければならないはずです。そういった時にやるべきことは、ファイルの大きさを取得し、その分のメモリを 確保してデータを読み込むことです。それには、fseek()関数、およびftell()関数が必要となります。まずは、以下のサンプルを見てください。

listex6-5:main.c
#include <stdio.h>
#include <stdlib.h>

void main() {
	FILE* file;
	int i,size;
	//	書き込むデータ
	char* rdata;
	//バイナリデータの書き込み
	file = fopen( "C:¥¥test¥¥test.bin", "rb" );
	if( file == NULL ){
		printf( "ファイルオープンに失敗しました。¥n" );
		exit( 1 );
	}
	//	ファイルの最後までシーク
	fseek(file, 0, SEEK_END);
	//	ファイルの大きさを取得
	size = ftell(file);
	//	メモリのサイズだけ、配列を動的に生成
	rdata = (char*)malloc(sizeof(char)*size);
	//	再るの最後までポインタを戻す
	fseek(file,0,SEEK_SET);
	fread( rdata, sizeof(char), size, file );
	fclose(file);          // ファイルをクローズ(閉じる)
	//	結果を表示
	for(i = 0; i < size ; i++){
		printf("%x ",rdata[i]);
	}
	printf("¥n");
	//	メモリ解放
	free(rdata);
}

fseek,ftell関数

このサンプルは、listex6-4で書き込まれた「test.bin」を読み出して表示するプログラムです。実行結果は同じなので省略します。ここで用いられているfseek関数およびftell関数は、 ファイルの位置を指定する関数です。仕様は以下の通りです。(表6-8)

表6-9.listex6-5で用いられる関数
関数書式意味使用例
fseek()fseek(ファイルポインタ,
移動バイト数,開始位置);
ファイルを開始位置から、移動バイト数移動する fseek(fp, 0L, SEEK_END);
ftell()ftell(ファイルポインタ);現在のファイル位置の値をバイト数で返す。ftell(fp);

一般にこの二つの関数はセットで使用されるケースがほとんどです。なお、fseek関数で指定される開始位置は、以下の通りです。(表6-10)(図6-2)

表6-10.fseek関数で用いられる開始位置
開始位置の定数意味
SEEK_SETファイルの先頭
SEEK_CURファイルの現在位置
SEEK_ENDファイルの終端

プログラムの流れ

では、これをもとにプログラムを解説していきましょう。ファイルオープンまでの流れはlistex6-4と同じなので省略します。まず、16行目のfseek()関数で、ファイルの読み出し位置を、ファイルの終端から0バイチ目、つまり、ファイルの終端にまで持っていきます。

図6-2.fseek関数の開始位置
fseek関数の開始位置

次に、18行目でftell()関数を用いて、位置を取得しています。ftell()では、ファイルの位置を取得してます。ファイルの読み出しの現在位置は終端にあるので、この時得られる値はファイルの大きさのバイト数に相当するはずです。なので、この値を変数sizeに代入し、保存します。

次に、このsizeをもとに、データを格納するメモリを確保(20行目)し、再びfseek()関数を用いて、ファイルの読み出し位置を先頭に戻し(22行目)、そのバイト数だけのデータを読み出します。(23行目)

最後にファイルを閉じ(24行目)、読みだしたデータの結果を表示してから、生成したメモリを開放し(31行目)、プログラムを終了します。

画像データのように、大きさが一定しないデータを読み出すときはこの方法が便利です。尚、fseek()およびftell()はテキストデータの読み出しの時にも使えます