C言語でのファイル操作には、人間が読める文字を扱う「テキストファイル」の他に、コンピュータが直接解釈するデータを扱う**「バイナリファイル」**の操作があります。画像ファイル(.jpg
)、音声ファイル(.mp3
)、実行ファイル(.exe
)などはすべてバイナリファイルです。
この記事では、バイナリファイルを効率的に読み書きするための必須関数である fread
, fwrite
と、ファイル内の好きな位置に移動できる fseek
の使い方を、分かりやすく解説します。
バイナリファイル操作の基本 fread
と fwrite
テキストファイル操作で使ったfprintf
やfscanf
は、数値を文字列に変換するなど、人間が読みやすい形に加工して読み書きします。一方、バイナリファイル操作では、データをメモリ上にあるそのままの形式(バイト列)で、直接ファイルに書き込んだり、読み込んだりします。
この操作で中心的な役割を担うのが fread
と fwrite
です。
ファイルオープンモード "rb"
と "wb"
バイナリファイルを扱う際は、fopen
関数のモードに b
を付けます。
"rb"
: バイナリファイルの読み込み (Read Binary)"wb"
: バイナリファイルの書き込み (Write Binary)
b
を付けることで、OSによる意図しない改行コードの変換などを防ぎ、データを正確に扱うことができます。
データを書き込む fwrite
関数
fwrite
は、メモリ上のデータ(変数や配列、構造体など)を、そのままのバイトイメージでファイルに書き込みます。
関数の形式: size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr
: 書き込みたいデータが格納されているメモリアドレス(変数名など)size
: データ1個あたりのサイズ(sizeof
演算子で取得)nmemb
: 書き出すデータの個数stream
: ファイルポインタ
使用例:構造体の配列をファイルに保存する
#include <stdio.h>
typedef struct {
char name[50];
int score;
} PlayerScore;
int main(void) {
FILE *fp;
// 書き込むデータ
PlayerScore ranking[] = {
{"Alice", 9800},
{"Bob", 8500},
{"Charlie", 9200}
};
if ((fp = fopen("scores.bin", "wb")) == NULL) {
printf("ファイルのオープンに失敗しました。\n");
return 1;
}
// ranking配列から3個のPlayerScoreデータをファイルに書き込む
fwrite(ranking, sizeof(PlayerScore), 3, fp);
printf("スコアデータを scores.bin に書き込みました。\n");
fclose(fp);
return 0;
}
データを読み込む fread
関数
fread
は、fwrite
と対になる関数で、バイナリファイルからデータをそのままの形式でメモリに読み込みます。
関数の形式: size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr
: 読み込んだデータを格納するメモリアドレス(変数名など)size
: データ1個あたりのサイズnmemb
: 読み込みたいデータの最大個数stream
: ファイルポインタ- 戻り値: 実際に読み込めたデータの個数。ファイルの終端などで要求した個数より少なくなることがあります。
使用例:ファイルから構造体の配列を読み込む
#include <stdio.h>
// fwriteの例と同じ構造体
typedef struct {
char name[50];
int score;
} PlayerScore;
int main(void) {
FILE *fp;
PlayerScore players[10]; // 読み込み用に十分な大きさの配列を用意
if ((fp = fopen("scores.bin", "rb")) == NULL) {
printf("ファイルのオープンに失敗しました。\n");
return 1;
}
// ファイルから最大10個のPlayerScoreデータを読み込む
size_t read_count = fread(players, sizeof(PlayerScore), 10, fp);
printf("scores.bin から %zu 件のデータを読み込みました。\n", read_count);
for (int i = 0; i < read_count; i++) {
printf("名前: %s, スコア: %d\n", players[i].name, players[i].score);
}
fclose(fp);
return 0;
}
実践例:バイナリファイルのコピープログラム
fread
と fwrite
の最も典型的な使い方が、ファイルのコピーです。画像などのファイルを、**一定サイズのバッファ(一時的なデータ置き場)**を介して少しずつ読み書きすることで、効率的にコピーできます。
#include <stdio.h>
#define BUFFER_SIZE 1024 // 1KBずつコピーする
int main(void) {
FILE *src_fp, *dest_fp;
unsigned char buffer[BUFFER_SIZE];
size_t read_size;
// コピー元ファイルを開く
if ((src_fp = fopen("source.jpg", "rb")) == NULL) {
printf("コピー元ファイルのオープンに失敗しました。\n");
return 1;
}
// コピー先ファイルを開く
if ((dest_fp = fopen("destination.jpg", "wb")) == NULL) {
printf("コピー先ファイルのオープンに失敗しました。\n");
fclose(src_fp);
return 1;
}
// コピー元から読み込み、コピー先に書き出すループ
while ((read_size = fread(buffer, 1, BUFFER_SIZE, src_fp)) > 0) {
fwrite(buffer, 1, read_size, dest_fp);
}
printf("ファイルのコピーが完了しました。\n");
fclose(dest_fp);
fclose(src_fp);
return 0;
}
このコードは、source.jpg
を1024バイトずつ読み込み、読み込めた分だけdestination.jpg
に書き出す、という処理をファイルの終わりまで繰り返します。
ランダムアクセス fseek
関数
fread
と fwrite
は、ファイルの先頭から順番に読み書きするシーケンシャルアクセスを行います。これに対し、fseek
を使うと、ファイル内の好きな位置に読み書きの現在地をジャンプさせることができます。これをランダムアクセスと呼びます。
関数の形式: int fseek(FILE *stream, long offset, int whence);
stream
: ファイルポインタoffset
:whence
を基準として移動するバイト数(正の値で順方向、負の値で逆方向)whence
: 移動の基準となる位置。以下のいずれかを指定します。SEEK_SET
: ファイルの先頭SEEK_CUR
: 現在の位置SEEK_END
: ファイルの末尾
使用例:先ほどのスコアデータの3番目の人だけ読み込む
// ...fopenまでの処理は同じ...
PlayerScore one_player;
int record_number = 3;
// 3番目のレコードの位置に移動する
// (3-1) * 構造体サイズ バイトだけファイルの先頭から移動
fseek(fp, (record_number - 1) * sizeof(PlayerScore), SEEK_SET);
// 現在位置から1件だけ読み込む
fread(&one_player, sizeof(PlayerScore), 1, fp);
printf("3番目のプレーヤー情報:\n");
printf("名前: %s, スコア: %d\n", one_player.name, one_player.score);
fclose(fp);
// ...
このように fseek
を使えば、巨大なファイルの中から必要なデータだけを直接読み出すことができ、非常に効率的です。
まとめ
- バイナリファイルの読み書きには
fread
とfwrite
を使い、ファイルオープン時には"rb"
や"wb"
を指定する。 - これらの関数は、データをメモリ上のバイトイメージのまま扱う。
fseek
を使うことで、ファイル内の任意の位置にアクセス(ランダムアクセス)でき、必要なデータだけを効率的に操作できる。
バイナリファイルの扱いは、画像処理やデータベースのような、より高度なアプリケーションを開発するための重要な一歩です。ぜひマスターして、C言語プログラミングの幅を広げてください。