WisdomSoft - for your serial experiences.

9.6 バイナリファイル

標準 C の関数を用いてテキストではない純粋な 2 進数列のデータをファイルに読み書きする方法を解説します。

9.6.1 純粋なデータ

fputs() 関数や fgets() 関数などは、テキストデータの入出力を行うときに便利でした。さらに、データの変換などを行う場合は fprintf() 関数や fscanf() が便利です。しかし、これらはASCII コードを扱う場合には便利ですが、純粋な数値データを入出力させたい場合は適切ではありません。テキストではなく生のバイナリデータを扱うには、これらの関数は使えません。

もちろん、方法のひとつとしては fprintf() や fscanf() を使ってテキストと数値の変換を採用することができます。保存する時はテキストデータに変換し、読み込み時に必要なデータは数値に変換するというものです。しかし、変換にかかる時間という問題もありますし、テキストデータは、他のソフトウェアを使って保存したデータを見られたり書き換えられる可能性があります。アプリケーションの種類によってはこれが好まれない場合もあることでしょう。

バイナリデータであれば、プログラムが直接数値として読み込むことが可能です。例えば構造体の値を入出力することを考えてください。これをテキストデータをして扱うとなれば、面倒な変換処理を記述しなければなりません。しかし、バイナリデータであれば直接読み書きすることができるのです。

バイナリデータを書き込むには fwrite() 関数を使います。

fwrite() 関数
size_t fwrite( const void*buffer, size_t size, size_t count, FILE *stream );*stream );

buffer には書き込むデータを格納しているメモリへのポインタを、size にはバイト単位でデータのサイズを、count にはデータの個数を指定します。例えば buffer が int 型の配列へのポインタを指すならば、size には sizeof(int) を指定し、count に書き込む要素の数を指定します。最後の引数 stream には書き込み対象のストリームを指定します。

buffer は void* へのポインタなので型は問われません。関数は一切の変換処理を行うことなく入出力を行います。関数が成功すれば実際に書き込んだ項目数を返します。count 引数で指定した値より低い値が返された場合、何らかのエラーが発生している可能性があります。

fwrite() 関数は引数が複雑のようにも思えますが、単純にいってしまえば buffer から size バイト単位で count 回 stream に書き込むということです。配列などの連続したデータを保存する時に便利です。

純粋なバイナリデータとして入出力を行う場合は fopen() 関数のアクセスモードの指定でバイナリデータであることを表す "b" を指定してください。書き込みモード "w" とあわせて "wb" というように指定することができます。テキストデータとして fwrite() 関数で書き込んだ場合、一部のコードが自動変換されてしまうなどの問題が発生する可能性があります。

コード1
#include <stdio.h>

typedef struct {
	int left , top , right , bottom;
} RECT;

int main() {
	FILE *file;
	RECT rect;

	printf("長方形の4つの角の座標を表す数値を入力してください>");
	scanf("%d %d %d %d" , &rect.left , &rect.top , &rect.right , &rect.bottom);

	printf("left=%d, top=%d, right=%d, bottom=%d\n",
		rect.left , rect.top , rect.right , rect.bottom);

	file = fopen("rect.dat" , "wb");
	if (file == NULL) {
		printf("ファイルを書き込みモードで開けませんでした");
		return 0;
	}
	fwrite(&rect , sizeof(RECT) , 1 , file);
	fclose(file);
	return 0;
}
実行結果
コード1 実行結果

コード1は、値の入力を求められるので 4 つの数値を入力してください。これらの値は長方形の座標として RECT 構造体型の変数に格納されます。その後、rect.dat という名前のファイルが生成され fwrite() 関数を用いて値を保存します。fwrite() 関数では保存する対象バッファイに rect 変数へのポインタを指定し、サイズに sizeof(RECT) を指定しています。rect 変数は配列ではないため count 引数には 1 を指定しています。

作成した rect.dat ファイルはテキストデータではなくバイナリデータなので、バイナリエディタなどで編集することができます。32ビット環境であれば 16 バイトのファイルが作成されているはずです。

バイナリデータを読み込むには fread() 関数を使います。

fread() 関数
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

基本的に引数は fwrite() 関数と同じです。buffer には読み込んだデータを保存するバッファへのポインタを指定し、size にはバイト単位でデータのサイズを、count にはデータの個数を、stream には読み込み対象のストリームをそれぞれ指定します。buffer は、読み込みに対して十分なメモリ領域が確保されていなければなりません。戻り値は実際に読み出した全項目の数を返します。この数が count より小さい場合は、エラーが発生したかファイルの終端に達しています。

コード2
#include <stdio.h>

typedef struct {
	int left , top , right , bottom;
} RECT;

int main() {
	RECT rect;
	FILE *file = fopen("rect.dat" , "rb");

	if (file == NULL) {
		printf("ファイルを読み込みモードで開けませんでした\n");
		return 0;
	}
	fread(&rect , sizeof(RECT) , 1 , file);
	fclose(file);

	printf("left=%d, top=%d, right=%d, bottom = %d\n" ,
		rect.left , rect.top , rect.right , rect.bottom);
	return 0;
}
実行結果
コード2 実行結果

コード2コード1で作成した rect.dat ファイルを読み込むためのプログラムです。fread() 関数を使って RECT 型の変数 rect に rect.dat ファイルからデータを読み込んでいることが確認できるでしょう。プログラムは最後に printf() 関数で rect 変数の値を表示します。