fseek() / ftell() / rewind() / feof()
| 対応: | C89(1989) |
|---|
ファイルの読み書き位置(ファイル位置指示子)を移動・取得したり、末尾に達したかどうかを判定したりする関数です。ランダムアクセスが必要なファイル処理に使います。
構文
// ファイル位置を基準点からオフセット分移動します。 // 戻り値: 成功時は 0、失敗時は非0。 int fseek(FILE *stream, long offset, int whence); // 現在のファイル位置をバイト数で返します。 // 戻り値: 現在位置、エラー時は -1L。 long ftell(FILE *stream); // ファイル位置を先頭に戻します(エラーフラグもクリアします)。 void rewind(FILE *stream); // ファイル末尾フラグが立っているか調べます。 // 戻り値: EOF に達していれば非0、そうでなければ 0。 int feof(FILE *stream);
whence(基準点)一覧
| 定数 | 基準点 | 概要 |
|---|---|---|
| SEEK_SET | ファイル先頭 | 先頭から offset バイト目に移動します。offset は 0 以上の値を指定します。 |
| SEEK_CUR | 現在位置 | 現在位置から offset バイト分前後に移動します。負の値で後退できます。 |
| SEEK_END | ファイル末尾 | 末尾から offset バイト分移動します。ファイルサイズを調べる際は offset に 0 を指定します。 |
サンプルコード
sample_fseek_ftell_rewind_feof.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// ファイルサイズを fseek + ftell で調べます。
FILE *fp = fopen("lines.txt", "rb");
if (fp == NULL) { perror("fopen"); return EXIT_FAILURE; }
fseek(fp, 0, SEEK_END); // 末尾に移動します。
long file_size = ftell(fp); // 現在位置 = ファイルサイズ(バイト)です。
printf("ファイルサイズ: %ld バイト\n", file_size);
rewind(fp); // 先頭に戻します。
printf("rewind後の位置: %ld\n", ftell(fp)); // 『0』と出力されます。
// SEEK_SET で先頭から7バイト目に移動して読み込みます。
fseek(fp, 7, SEEK_SET);
char buf[64];
if (fgets(buf, sizeof(buf), fp) != NULL) {
printf("7バイト目から: %s", buf); // 2行目の内容が出力されます。
}
// feof でループ終了を確認します(推奨: 戻り値で判定する方が確実です)。
rewind(fp);
int ch;
int count = 0;
while ((ch = fgetc(fp)) != EOF) {
count++;
}
if (feof(fp)) {
printf("正常に末尾に達しました。文字数: %d\n", count);
}
fclose(fp);
return 0;
}
コンパイルして実行すると次のようになります。
gcc sample_fseek_ftell_rewind_feof.c -o sample_fseek_ftell_rewind_feof ./sample_fseek_ftell_rewind_feof ファイルサイズ: 45 rewind後の位置: 0 7バイト目から: user/documents/report.txt 正常に末尾に達しました。文字数: 45
ファイルサイズを取得して全読み込みする
fseek と ftell でファイルサイズを調べてから、必要なバッファを確保してファイル全体を読み込む方法です。
fseek_read_all.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("sample.txt", "w");
if (fp == NULL) { perror("fopen"); return 1; }
fputs("item_a\nitem_b\nitem_c\n", fp);
fclose(fp);
fp = fopen("sample.txt", "rb");
if (fp == NULL) { perror("fopen"); return 1; }
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp);
char *buf = malloc(size + 1);
if (buf == NULL) { fclose(fp); return 1; }
fread(buf, 1, size, fp);
buf[size] = '\0';
fclose(fp);
printf("ファイル全体(%ld バイト):\n%s", size, buf);
free(buf);
return 0;
}
コンパイルして実行すると次のようになります。
gcc fseek_read_all.c -o fseek_read_all ./fseek_read_all ファイル全体(21 バイト): item_a item_b item_c
SEEK_CUR でランダムアクセス
SEEK_CUR を使うと現在位置からの相対移動ができます。固定サイズのレコードを持つバイナリファイルのランダムアクセスに便利です。
fseek_seek_cur.c
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
char name[16];
} Record;
int main(void) {
Record recs[] = {
{1, "alpha"},
{2, "beta"},
{3, "gamma"}
};
int n = (int)(sizeof(recs) / sizeof(recs[0]));
FILE *fp = fopen("records.bin", "wb");
if (fp == NULL) { perror("fopen"); return 1; }
fwrite(recs, sizeof(Record), n, fp);
fclose(fp);
fp = fopen("records.bin", "rb");
if (fp == NULL) { perror("fopen"); return 1; }
fseek(fp, sizeof(Record), SEEK_SET);
Record r;
fread(&r, sizeof(Record), 1, fp);
printf("レコード2: id=%d name=%s\n", r.id, r.name);
fseek(fp, sizeof(Record), SEEK_CUR);
long pos = ftell(fp);
printf("現在位置: %ld バイト\n", pos);
fclose(fp);
remove("records.bin");
return 0;
}
コンパイルして実行すると次のようになります。
gcc fseek_seek_cur.c -o fseek_seek_cur ./fseek_seek_cur レコード2: id=2 name=beta 現在位置: 60 バイト
よくあるミス
よくあるミス: while (!feof(fp)) パターン
feof() はファイル末尾フラグを確認するだけです。while(!feof(fp)) をループ条件にすると最後のデータを2回処理するバグが生じます。
#include <stdio.h>
int main(void) {
FILE *fp = fopen("test.txt", "w");
if (fp == NULL) return 1;
fputs("hello\n", fp);
fclose(fp);
fp = fopen("test.txt", "r");
if (fp == NULL) return 1;
char buf[32];
/* NG: 最後の行が2回出力される */
while (!feof(fp)) {
if (fgets(buf, sizeof(buf), fp) != NULL) {
printf("NG版: %s", buf);
}
}
fclose(fp);
return 0;
}
fgets() の戻り値で NULL を判定する正しいパターンを使います。
feof_ok.c
#include <stdio.h>
int main(void) {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) return 1;
char buf[32];
/* OK: fgets の戻り値で EOF を判定する */
while (fgets(buf, sizeof(buf), fp) != NULL) {
printf("OK版: %s", buf);
}
fclose(fp);
return 0;
}
修正後は次の通りです。
gcc feof_ok.c -o feof_ok ./feof_ok OK版: hello
概要
テキストモードで開いたファイルへの fseek() は、SEEK_SET + ftell() が返した値の組み合わせ以外では動作が処理系依存になります。ランダムアクセスが必要な場合は必ずバイナリモード(『"rb"』)で開いてください。
『feof()』はファイル末尾フラグを確認するだけです。『while (!feof(fp))』をループ条件にすると最後のデータを2回処理するバグが生じることがあります。かわりに『fgets()』や『fgetc()』の戻り値で EOF を判定するパターンを推奨します。
ファイルの読み書き関数全般については『fread() / fwrite()』および『fgets() / fputs()』も参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。