ファイルをいったん読んだり書いたりした後、もう一度最初から処理したいことは意外とよくあります。
そのときに役立つのがrewind
です。
本記事では、C言語初心者の方向けにrewind
の動作、使いどころ、安全な手順、注意点を順序立てて詳しく解説します。
加えて、追記後の全体読み直しやEOFからの再読などのケース別サンプルも示します。
rewindとは?
ファイル先頭へ戻す
rewind
は、ストリームの読み書き位置をファイルの先頭に戻す標準関数です。
ヘッダ#include <stdio.h>
をインクルードして利用します。
プロトタイプは次のとおりです。
/* stdio.h より */
void rewind(FILE *stream);
FILE*
を受け取り、そのストリームのファイル位置を先頭へセットします。
戻り値はありませんので、成功・失敗を返しません。
使いどころ
同じファイルを「一度処理してから、もう一度先頭から読み直したい」ときに使います。
例えば次のような場面です。
文章で順に説明します。
- ファイルに書いた内容を、すぐに先頭から読み直して検証したいとき。
- 1回目の走査で件数やサイズを数え、2回目の走査で実データを読み込む2パス処理。
- 追記(
a+
)した後に、ファイル全体を表示したいシンプルなビューア。 - バイナリファイルに構造体配列を書き出してから、先頭に戻して読み戻し確認をしたいとき。
ファイルを閉じて開き直すより軽量で、既存のFILE*
をそのまま使えるのが利点です。
EOFとエラー状態もクリア
重要なのは、rewind
がEOF(終端)とエラーの状態フラグをクリアする点です。
読み取りでEOFに到達したあとはfeof(stream)
が真になりますが、rewind
を呼ぶとこのフラグが落ち、先頭からの再読が可能になります。
同様にferror(stream)
もクリアされます。
以下の短い例は、EOFフラグがrewind
で落ちることを確認します。
#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の使い方と手順
書いた直後の再読や読んだ直後の再読など、典型的な流れをひとつのサンプルで示します。
要点はfflush
とrewind
の位置です。
ファイルを開く
更新(読み書き両用)で開くと、1つのFILE*
で読みと書きを切り替えられます。
用途に応じてモードを選びます。
w+
: 新規作成または上書き、読み書き可r+
: 既存ファイルを読み書き可で開く(上書きはしない)a+
: 追記専用の書き込み位置、読みも可
次の表は簡単な目安です。
モード | 既存内容 | 書き込み位置 | 読み | 追記 |
---|---|---|---|---|
w+ | 破棄して空 | 先頭 | 可 | 不可(任意位置へ上書き) |
r+ | 温存 | 現在位置(先頭) | 可 | 不可(任意位置へ上書き) |
a+ | 温存 | 常に末尾 | 可 | 可(必ず末尾へ) |
読む/書く → rewindで先頭へ
まずは書式付きで書いてから、先頭に戻して読み直してみます。
#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
後は、fgets
やfscanf
、fread
など、通常どおりの読み込みができます。
テキストでもバイナリでも手順は同じです。
以下はバイナリの例です。
配列を書き出してから先頭に戻して読み直します。
#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
で原因を出力するとよいです。
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
してから読みます。
#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とエラー状態がクリアされ、先頭から再読できます。
#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)
は失敗します。stdout
やstderr
も端末に接続されるのが一般的で、やはり位置決めできません。- シェルのリダイレクトで通常ファイルに接続されている場合は位置決め可能なこともありますが、環境依存であり、戻り値がないため失敗を検出しづらいです。
どうしても必要なら、標準入出力を安易にrewind
に頼らず、入力をいったんファイルに保存する、またはメモリに取り込んで再処理できる設計にするのが安全です。
位置決めの可否を判定したい場合は、別記事で扱うfseek
の成否で判定するのが現実的です。
以下は動作環境によっては失敗する可能性のある例です(出力は省略)。
あくまで注意喚起用の参考コードです。
#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
を安心して活用できます。