閉じる

【C言語】 rewindでファイル先頭へ戻す 使い方と安全な手順

ファイルをいったん読んだり書いたりした後、もう一度最初から処理したいことは意外とよくあります。

そのときに役立つのがrewindです。

本記事では、C言語初心者の方向けにrewindの動作、使いどころ、安全な手順、注意点を順序立てて詳しく解説します。

加えて、追記後の全体読み直しやEOFからの再読などのケース別サンプルも示します。

rewindとは?

ファイル先頭へ戻す

rewindは、ストリームの読み書き位置をファイルの先頭に戻す標準関数です。

ヘッダ#include <stdio.h>をインクルードして利用します。

プロトタイプは次のとおりです。

C言語
/* stdio.h より */
void rewind(FILE *stream);

FILE*を受け取り、そのストリームのファイル位置を先頭へセットします。

戻り値はありませんので、成功・失敗を返しません。

使いどころ

同じファイルを「一度処理してから、もう一度先頭から読み直したい」ときに使います。

例えば次のような場面です。

文章で順に説明します。

  • ファイルに書いた内容を、すぐに先頭から読み直して検証したいとき。
  • 1回目の走査で件数やサイズを数え、2回目の走査で実データを読み込む2パス処理。
  • 追記(a+)した後に、ファイル全体を表示したいシンプルなビューア。
  • バイナリファイルに構造体配列を書き出してから、先頭に戻して読み戻し確認をしたいとき。

ファイルを閉じて開き直すより軽量で、既存のFILE*をそのまま使えるのが利点です。

EOFとエラー状態もクリア

重要なのは、rewindEOF(終端)とエラーの状態フラグをクリアする点です。

読み取りでEOFに到達したあとはfeof(stream)が真になりますが、rewindを呼ぶとこのフラグが落ち、先頭からの再読が可能になります。

同様にferror(stream)もクリアされます。

以下の短い例は、EOFフラグがrewindで落ちることを確認します。

C言語
#include <stdio.h>
#include <string.h>

int main(void) {
    const char *path = "eof_demo.txt";

    // 最初に内容を作る(上書き)
    FILE *fp = fopen(path, "w+"); // 読み/書き両用で新規作成
    if (!fp) {
        perror("fopen");
        return 1;
    }
    fputs("ABC\nDEF\n", fp);
    fflush(fp);

    // 先頭へ戻して全て読む(EOF到達)
    rewind(fp);

    char buf[8];
    while (fgets(buf, sizeof buf, fp)) {
        fputs(buf, stdout);
    }

    // EOFとエラーのフラグを表示
    printf("feof before rewind: %d\n", feof(fp));
    printf("ferror before rewind: %d\n", ferror(fp));

    // 再度先頭へ
    rewind(fp);
    printf("feof after rewind: %d\n", feof(fp));
    printf("ferror after rewind: %d\n", ferror(fp));

    fclose(fp);
    return 0;
}
実行結果
ABC
DEF
feof before rewind: 1
ferror before rewind: 0
feof after rewind: 0
ferror after rewind: 0

rewindの使い方と手順

書いた直後の再読読んだ直後の再読など、典型的な流れをひとつのサンプルで示します。

要点はfflushrewindの位置です。

ファイルを開く

更新(読み書き両用)で開くと、1つのFILE*で読みと書きを切り替えられます。

用途に応じてモードを選びます。

  • w+: 新規作成または上書き、読み書き可
  • r+: 既存ファイルを読み書き可で開く(上書きはしない)
  • a+: 追記専用の書き込み位置、読みも可

次の表は簡単な目安です。

モード既存内容書き込み位置読み追記
w+破棄して空先頭不可(任意位置へ上書き)
r+温存現在位置(先頭)不可(任意位置へ上書き)
a+温存常に末尾可(必ず末尾へ)

読む/書く → rewindで先頭へ

まずは書式付きで書いてから、先頭に戻して読み直してみます。

C言語
#include <stdio.h>

int main(void) {
    const char *path = "rewind_basic.txt";

    // 1) w+ で新規作成し、読み書き可能に
    FILE *fp = fopen(path, "w+");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    // 2) 書式付きで書く
    fprintf(fp, "id=%d,name=%s\n", 1001, "Alice");
    fprintf(fp, "id=%d,name=%s\n", 1002, "Bob");

    // 3) 書いた直後は出力バッファを明示的に流す
    fflush(fp);

    // 4) 先頭へ戻す
    rewind(fp);

    // 5) 先頭から読み直す
    char line[128];
    while (fgets(line, sizeof line, fp)) {
        // 読み取った行を標準出力へ
        fputs(line, stdout);
    }

    fclose(fp);
    return 0;
}
実行結果
id=1001,name=Alice
id=1002,name=Bob

書いた直後はfflushして安全に

重要

C標準では、出力の直後に入力へ切り替える場合fflushまたは位置決め関数(rewindなど)の呼び出しが必要です。

これはr+w+などの更新モードで特に大切です。

書いた直後はfflushしてからrewindと覚えると安全です。

一方、入力の直後に出力へ切り替える場合はrewindなどの位置決め関数が必要です。

fflushは出力ストリームのための関数であり、入力専用ストリームに対するfflushの挙動は未規定ですので避けます。

先頭から読み直す

rewind後は、fgetsfscanffreadなど、通常どおりの読み込みができます。

テキストでもバイナリでも手順は同じです。

以下はバイナリの例です。

配列を書き出してから先頭に戻して読み直します。

C言語
#include <stdio.h>

int main(void) {
    const char *path = "rewind_binary.bin";

    FILE *fp = fopen(path, "wb+"); // バイナリで読み書き
    if (!fp) {
        perror("fopen");
        return 1;
    }

    // int 配列を書き込む
    int out[] = {10, 20, 30, 40, 50};
    size_t wrote = fwrite(out, sizeof(int), 5, fp);
    if (wrote != 5) {
        perror("fwrite");
        fclose(fp);
        return 1;
    }

    // 書いた直後に flush してから rewind
    fflush(fp);
    rewind(fp);

    // 読み戻し
    int in[5] = {0};
    size_t read = fread(in, sizeof(int), 5, fp);
    if (read != 5) {
        perror("fread");
        fclose(fp);
        return 1;
    }

    // 取り出した値を確認
    for (size_t i = 0; i < 5; ++i) {
        printf("in[%zu]=%d\n", i, in[i]);
    }

    fclose(fp);
    return 0;
}
実行結果
in[0]=10
in[1]=20
in[2]=30
in[3]=40
in[4]=50

安全に使うための注意点

NULLチェック

必ずfopenの戻り値をNULLチェックします。

ファイルが開けなければrewindどころではありません。

エラー時にはperrorで原因を出力するとよいです。

C言語
FILE *fp = fopen("file.txt", "r+");
if (!fp) {
    perror("fopen");
    return 1;
}

rewindは戻り値なし

rewind戻り値を返しません

つまり、成功したかどうかを直接判定できません。

シーク不可なストリーム(後述)に対して呼ぶと失敗し、エラー状態が再度立つ可能性があります。

  • どうしても判定したい場合は、位置決めが可能かどうかを事前に確認します。代表的にはfseek(stream, 0, SEEK_SET)の成否で検出します(詳細は別記事)。rewind相当の挙動を自前で再現したいときは、成功時にclearerr(stream)する、といった手もあります。

シーク不可なストリーム

すべてのストリームでrewindが使えるわけではありません

ファイル(普通のディスクファイル)は基本的にOKですが、端末、パイプ、ソケットなどは位置決めできません。

種別rewindの可否備考
通常ファイルHDD/SSD上のファイル先頭へ戻せる
端末/コンソール対話的な標準入力/出力×位置決め不可が一般的
パイプ/ソケットプロセス間通信/ネットワーク×ストリームは一方向に流れる
メモリマップ的API実装依存使用APIにより異なる

シーク不可なストリームではrewindを前提にしない設計にしてください。

必要なら、データをいったんファイルに落としてから処理する、もしくはメモリ上に読み込んで再処理する方法が安全です。

バッファリングの注意

C標準の規則を要点だけ抑えます。

  • 書き込みのあとに読み込みへ切り替えるとき: fflushまたはrewind等の位置決め関数が必須です。
  • 読み込みのあとに書き込みへ切り替えるとき: rewind等の位置決め関数が必須です。fflushは出力用なので、このケースの対策にはなりません。
  • 入力専用ストリーム("r")でfflushを呼ぶのは避けます。挙動は規格上未規定です。

この規則を守ると、「読み書きの切り替え時の未定義動作」を確実に回避できます。

テキスト/バイナリで利用可

rewindテキストモードでもバイナリモードでも同じように使えます

Windowsのようにテキストモードで改行変換がある環境でも、rewindは単純に「先頭へ戻す」だけです。

ただし、ファイルを開くモード自体は正しく選ぶ(テキストなら"r+"、バイナリなら"rb+"など)ようにしてください。

ケース別の使い方

追記後に全体を読む

a+で追記した内容を含め、ファイルの最初から全部表示する例です。

追記後にfflushし、rewindしてから読みます。

C言語
#include <stdio.h>

int main(void) {
    const char *path = "append_demo.txt";

    // a+ で既存を温存しつつ、追記と読取り
    FILE *fp = fopen(path, "a+");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    // 末尾へ追記(常にファイル末尾に書かれる)
    fprintf(fp, "append line: %d\n", 1);

    // 書いた直後に flush
    fflush(fp);

    // 先頭に戻して全体を読む
    rewind(fp);

    char line[128];
    while (fgets(line, sizeof line, fp)) {
        fputs(line, stdout);
    }

    fclose(fp);
    return 0;
}
実行結果
append line: 1

(ファイルが既に存在していた場合は、既存行の後ろに1行追加され、rewind後の読み出しで全行が表示されます)

EOFになったらrewindでリセット

EOFに達すると、そのままでは読み続けてもデータは戻りません。

rewindでEOFとエラー状態がクリアされ、先頭から再読できます。

C言語
#include <stdio.h>

int main(void) {
    const char *path = "eof_reset.txt";

    // デモ用に作る
    FILE *fp = fopen(path, "w+");
    if (!fp) { perror("fopen"); return 1; }
    fputs("once\nmore\n", fp);
    fflush(fp);

    // 先頭から最後まで読む(EOF到達)
    rewind(fp);
    char buf[64];
    while (fgets(buf, sizeof buf, fp)) {
        fputs(buf, stdout);
    }

    // ここでさらに読もうとしても何も読めない
    char *p = fgets(buf, sizeof buf, fp);
    printf("after EOF, fgets returns: %p\n", (void*)p);

    // rewindでリセットして再読
    rewind(fp);
    while (fgets(buf, sizeof buf, fp)) {
        fputs(buf, stdout);
    }

    fclose(fp);
    return 0;
}
実行結果
once
more
after EOF, fgets returns: (nil)
once
more

標準入出力(stdin, stdout, stderr)の注意

標準ストリームに対するrewindは基本的に推奨しません

理由は次のとおりです。

  • stdinが端末やパイプに接続されている場合、位置決めできないためrewind(stdin)は失敗します。
  • stdoutstderrも端末に接続されるのが一般的で、やはり位置決めできません。
  • シェルのリダイレクトで通常ファイルに接続されている場合は位置決め可能なこともありますが、環境依存であり、戻り値がないため失敗を検出しづらいです。

どうしても必要なら、標準入出力を安易にrewindに頼らず、入力をいったんファイルに保存する、またはメモリに取り込んで再処理できる設計にするのが安全です。

位置決めの可否を判定したい場合は、別記事で扱うfseekの成否で判定するのが現実的です。

以下は動作環境によっては失敗する可能性のある例です(出力は省略)。

あくまで注意喚起用の参考コードです。

C言語
#include <stdio.h>

int main(void) {
    // 環境によっては失敗する(端末やパイプは不可)
    rewind(stdin);  // 位置決め不可のときは効果なし、エラーが立つ場合も
    rewind(stdout); // 同上
    rewind(stderr); // 同上

    return 0;
}

まとめ

rewindはファイルの先頭に読み書き位置を戻し、EOFとエラー状態を同時にクリアする便利な関数です。

初心者の方は次のポイントを押さえると安全に使えます。

文章で整理します。

まず、更新モード(r+/w+/a+)で同一FILE*を読み書きする場合、書いた直後はfflush、そしてrewindで先頭から読み直すのが基本です。

読みから書きへ切り替える場合はrewind等の位置決めを挟みます。

次に、戻り値がないため失敗検出ができないこと、シーク不可なストリームでは使えないことを理解しておきましょう。

また、標準入出力やパイプは位置決めできないことが多く、rewindに頼らない設計が無難です。

テキストでもバイナリでも使えますが、開くモードは"r+""rb+"など適切に選んでください。

これらを踏まえれば、「一度処理してからもう一度最初から」という場面でrewindを安心して活用できます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!