C言語入門|バイナリ入出力の基本:fwriteとfread

文字ではなく、データそのものを扱う

これまでの記事では、
fputc や fputs、fprintf などを使って テキストファイル を読み書きしてきました。

テキストファイルは、人が読める文字を前提とした形式なので、

  • 改行コード
  • 文字コード
  • 書式指定

といったルールを意識する必要がありましたね。

一方で、次のような場面ではどうでしょう。

  • 構造体をそのまま保存したい。
  • 数値データを高速に読み書きしたい。
  • 文字コードや改行を一切気にしたくない。

そんなときに活躍するのが、
サイズ指定で読み書きするバイナリ入出力 です。

サイズ指定で読み書きするという考え方

バイナリ入出力では、

  • 文字
  • 書式

といった概念は登場しません。

代わりに使うのは、

  • 何バイトのデータを
  • いくつ分

という、純粋なサイズ指定 です。

この考え方を実現する関数が、
fwrite と fread です。

fwrite:指定したサイズで書き込む

fwrite は、
メモリ上のデータを、そのままの形でファイルに書き込む関数 です。

書式

size_t fwrite(const void* wp, size_t s, size_t n, FILE* fp)

引数の意味

引数内容
wp書き込むデータの先頭アドレス
sデータ1個あたりのバイト数
n書き込むデータの個数
fp書き込み先ファイルのファイルポインタ

戻り値

意味
n と同じ正常にすべて書き込み成功
n より小さい途中で失敗

つまり、
s × n バイト分を書き込もうとする
という動作になります。

fread:指定したサイズで読み取る

fread は、
ファイルから指定サイズ分のデータをまとめて読み取る関数 です。

書式

size_t fread(void* rp, size_t s, size_t n, FILE* fp)

引数の意味

引数内容
rp読み取ったデータを格納する領域
sデータ1個あたりのバイト数
n読み取るデータの個数
fp読み取り元ファイルのファイルポインタ

戻り値

意味
n と同じ正常にすべて読み取り成功
n より小さいファイル終端または失敗

テキスト系の関数と違い、
EOF を直接扱わない点が特徴です。

fwrite / fread を使ったサンプル例

次の例では、
構造体の配列をバイナリファイルに保存し、
その内容を読み戻して画面に表示します。

サンプルプログラム

プロジェクト名:14-9-1 ソースファイル名: sample14-9-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

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

typedef struct {
    int id;
    double value;
} Data;

int main(void)
{
    FILE* fp;
    Data out[3] = {
        {1, 10.5},
        {2, 20.0},
        {3, 30.25}
    };
    Data in[3];

    fp = fopen("data.bin", "wb");
    if (fp == NULL) {
        exit(1);
    }

    fwrite(out, sizeof(Data), 3, fp);
    fclose(fp);

    fp = fopen("data.bin", "rb");
    if (fp == NULL) {
        exit(1);
    }

    fread(in, sizeof(Data), 3, fp);
    fclose(fp);

    for (int i = 0; i < 3; i++) {
        printf("id=%d value=%.2f\n", in[i].id, in[i].value);
    }

    return 0;
}

このプログラムで起きていること

この処理では、

  1. 構造体 Data を 3 個まとめて書き込み
  2. 書き込んだバイト列をそのまま読み取り
  3. メモリ上で元の構造体として復元

という流れになっています。

文字コードや改行は一切関係なく、
メモリ上の状態を丸ごと保存している
と考えるとイメージしやすいでしょう。

テキスト入出力との違いを整理する

項目テキスト入出力バイナリ入出力
単位文字・行バイト
書式指定必要不要
可読性人が読める人は読めない
高速性普通高速
主な用途設定・CSVセーブデータ・構造体

バイナリ入出力で気をつける点

とても便利な fwrite / fread ですが、注意点もあります。

  • 構造体のサイズは環境依存
  • CPU や OS が変わると互換性がない場合がある。
  • エンディアンの違いが影響することがある。

そのため、

  • 同一環境で使うデータ
  • 一時的な保存
  • 高速処理が必要な場面

に向いている方法だと覚えておきましょう。

ここまでのまとめ

この節のポイントは次のとおりです。

  • fwrite / fread はサイズ指定で読み書きする。
  • データを文字として解釈しない。
  • 構造体や配列をそのまま保存できる。
  • 高速だが環境依存の注意が必要

バイナリ入出力を理解すると、
C言語らしい低レベルなデータ操作 が一気に身近になります。