閉じる

【C言語】 fseek/ftell入門|ファイルポインタ操作とファイルサイズ取得の実践例

C言語でファイルを扱うとき、ファイルのどこを読み書きしているかを正確に把握できると、処理の自由度が一気に高まります。

本記事ではfseekftellを中心に、ファイルポインタの操作とファイルサイズ取得の手順を、図解とサンプルコードを交えて詳しく解説します。

基礎から応用まで順を追って学べる内容になっています。

fseekとftellとは何かを理解する

fseekとは

fseekは、ファイルポインタの位置(ファイル位置指示子)を任意の場所に移動するための関数です。

C標準ライブラリのstdio.hに定義されており、主に次のような目的で使われます。

  • ファイルの先頭や末尾に一気に移動する
  • 指定したバイト位置にジャンプしてランダムアクセスする
  • 読み込み位置を少し戻したり進めたりする

ファイルは通常、先頭から順番に読み書きしますが、fseekを使うと任意の位置へ自由に移動できるため、ログ解析やデータベース的なアクセスなど、より高度な処理が可能になります。

ftellとは

ftellは、現在のファイルポインタの位置(ファイル位置指示子)をバイトオフセットとして取得する関数です。

こちらもstdio.hに定義されています。

  • ファイルのどこまで読み進めたかを数値で知りたいとき
  • fseekと組み合わせてファイルサイズを取得したいとき
  • ログなどで「どの位置まで処理したか」を記録したいとき

といった場面で活躍します。

FILE構造体とファイルポインタの関係

C言語でファイルを扱うときは、FILE*型の変数(ファイルポインタ)を使います。

これは実際にはライブラリ内部で管理されているFILE構造体へのポインタです。

FILE構造体は、典型的に次のような情報を持っています。

  • バッファ(読み書き用の一時領域)
  • 現在のファイル位置指示子
  • エラーフラグやEOF(終端)フラグ
  • オープンモード(読み込み専用、書き込み専用など)

fseekとftellが操作・参照しているのは、このFILE構造体が持つ「現在位置」の情報です。

したがって、ファイルポインタを共有する関数間で、位置の変更は互いに影響を与えることになります。

fseekの基本的な使い方

fseekの関数プロトタイプと戻り値

まずはfseekの宣言(プロトタイプ)を確認します。

C言語
#include <stdio.h>

/* fseekのプロトタイプ */
int fseek(FILE *stream, long offset, int origin);

戻り値はint型で、意味は次の通りです。

  • 成功した場合: 0
  • 失敗した場合: 0以外(通常は-1)

したがって、エラー判定はif (fseek(...) != 0)のように行います。

戻り値の詳細な値は処理系依存のことが多いため、「0なら成功、それ以外は失敗」と覚えておくとよいです。

第2引数(offset)と第3引数(origin)の意味

fseekの第2引数offsetと第3引数originはセットで動作します。

  • origin: 基準位置を表す定数(後述のSEEK_SETなど)
  • offset: 基準位置からの移動量(バイト単位、正または負)

fseekの動作は、文章で表現すると次のようになります。

「originで指定された基準位置から、offsetバイト分だけ相対的に移動した場所にファイルポインタをセットする」

たとえば、ファイルの先頭から100バイト目に移動したい場合は、次のように記述します。

C言語
/* ファイル先頭から100バイト目へ移動 */
if (fseek(fp, 100L, SEEK_SET) != 0) {
    /* エラー処理 */
}

SEEK_SET・SEEK_CUR・SEEK_ENDの違い

fseekの第3引数originには、次の3つの定数のいずれかを指定します。

  • SEEK_SET: ファイルの先頭からのオフセットを指定する
  • SEEK_CUR: 現在位置からの相対オフセットを指定する
  • SEEK_END: ファイルの末尾からの相対オフセットを指定する

概要を表にまとめると次のようになります。

origin定数基準位置の意味代表的な使い方の例
SEEK_SETファイルの先頭固定レコードのn番目に直接ジャンプするなど
SEEK_CUR現在位置現在位置から少し戻る・少し進める処理
SEEK_ENDファイルの末尾末尾から逆方向に読む、末尾位置の取得など

たとえば、末尾から10バイト前に移動したい場合は、次のように記述します。

C言語
/* ファイル末尾から10バイト戻った位置へ移動 */
if (fseek(fp, -10L, SEEK_END) != 0) {
    /* エラー処理 */
}
注意

offsetを負の値にできるかどうかは処理系に依存する部分もあります

多くの環境では上記のように動作しますが、特にテキストモードでは注意が必要です。

テキストファイルとバイナリファイルでの挙動の違い

C言語では、fopenでファイルを開く際にモードを指定します。

  • テキストモード: "r""w""a"など
  • バイナリモード: "rb""wb""ab"など

テキストモードでは、特にWindows環境で改行コードの変換(LFとCRLFの変換)が行われることがあります。

これにより、ftellで得られる値と実際のディスク上のバイト数が一致しないことがあります。

一方でバイナリモードでは、基本的にファイル内容は一切変換されず、そのまま読み書きされます。

そのため、

  • ファイルサイズ取得やバイトオフセットの計算を正確に行いたい場合
  • バイナリ形式のデータ(画像、音声、独自フォーマットなど)を扱う場合

は、必ずバイナリモード(例: “rb”)で開くようにすることをおすすめします。

fseek使用時のエラー処理と注意点

fseekを使用する際には、いくつかの注意点があります。

1つ目は戻り値のチェックです。

fseekが失敗した場合、戻り値は0以外になります。

さらに詳しい原因を知るにはperror関数やerrnoを使います。

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

void example_fseek_error(FILE *fp) {
    if (fseek(fp, 0L, SEEK_END) != 0) {
        perror("fseek failed");  /* 標準エラー出力にエラーメッセージを出す */
        /* 必要に応じてerrnoをチェック */
    }
}

実行結果例(状況により変わります):

実行結果
fseek failed: Illegal seek

2つ目は有効範囲外へのシークです。

一般に、

  • ファイルの先頭より前(負の位置)
  • 実装が許容しないほど大きな位置

などへ移動しようとするとfseekは失敗します。

3つ目はテキストストリームでは、fseekの動作が制限されることがある点です。

特に改行変換を伴う環境では、「ファイル先頭からの任意のバイト数へのシーク」が保証されていないことがあるため、仕様書(C規格)上も注意が促されています。

テキストファイルでfseekを使う場合は、

  • offset == 0SEEK_SETまたはSEEK_CURを使う
  • offset == 0SEEK_ENDを使う

といった限定されたパターンに留めるのが安全です。

ftellでファイルサイズを取得する実践

ftellの関数プロトタイプと戻り値の型

ftellの宣言は次の通りです。

C言語
#include <stdio.h>

/* ftellのプロトタイプ */
long ftell(FILE *stream);

戻り値の型はlong型です。

意味は次の通りです。

  • 成功した場合: 現在のファイル位置(基準はSEEK_SET、単位はおおむねバイト)
  • 失敗した場合: -1Lを返し、errnoが設定される

したがって、エラー判定はif (pos == -1L)のように行います。

ftellで現在のファイル位置を取得する

実際にftellを使って、読み込みの進行状況を確認する簡単な例を示します。

C言語
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("sample.txt", "r");  /* テキストファイルを開く */
    if (fp == NULL) {
        perror("fopen failed");
        return 1;
    }

    int ch;
    long pos;

    /* 1文字読み込んでから位置を確認する処理を数回繰り返す */
    for (int i = 0; i < 5; i++) {
        ch = fgetc(fp);  /* 1文字読み込む */
        if (ch == EOF) {
            if (feof(fp)) {
                printf("EOF reached.\n");
            } else {
                perror("fgetc failed");
            }
            break;
        }

        pos = ftell(fp);  /* 現在位置を取得 */
        if (pos == -1L) {
            perror("ftell failed");
            break;
        }

        printf("Read char '%c', current position: %ld\n", ch, pos);
    }

    fclose(fp);
    return 0;
}

出力例(ファイル内容により異なります):

実行結果
Read char 'H', current position: 1
Read char 'e', current position: 2
Read char 'l', current position: 3
Read char 'l', current position: 4
Read char 'o', current position: 5

このように、ftellは現在の読み込み位置を数値として把握するのに便利です。

fseekとftellを組み合わせたファイルサイズの取得手順

ファイルサイズを取得する典型的な手順は次の通りです。

  1. fseek(fp, 0L, SEEK_END)でファイル末尾に移動する
  2. ftell(fp)で現在位置を取得する
  3. 必要ならfseek(fp, 0L, SEEK_SET)で先頭に戻す

これにより、ファイルサイズ(バイト数)を簡単に知ることができます

実践例

実際に、バイナリファイルのサイズを取得して表示するサンプルコードを示します。

C言語
#include <stdio.h>

/* 指定したファイルのサイズを取得して表示するプログラム */
int main(void) {
    const char *filename = "data.bin";

    /* バイナリ読み込みモードで開くことが重要 */
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        perror("fopen failed");
        return 1;
    }

    /* 1. 末尾へ移動 */
    if (fseek(fp, 0L, SEEK_END) != 0) {
        perror("fseek to end failed");
        fclose(fp);
        return 1;
    }

    /* 2. 現在位置 = ファイルサイズ */
    long size = ftell(fp);
    if (size == -1L) {
        perror("ftell failed");
        fclose(fp);
        return 1;
    }

    /* 3. 必要なら先頭に戻す */
    if (fseek(fp, 0L, SEEK_SET) != 0) {
        perror("fseek to beginning failed");
        fclose(fp);
        return 1;
    }

    printf("File: %s\n", filename);
    printf("Size: %ld bytes\n", size);

    fclose(fp);
    return 0;
}
実行結果
File: data.bin
Size: 4096 bytes

ここで重要なのは、ファイルを”rb”(バイナリモード)で開いている点です。

これにより、改行コード変換の影響を受けず、実際のバイト数を正確に取得できます。

大きなファイルサイズとオーバーフローへの注意点

ftellの戻り値の型はlongです。

多くの32bit環境ではlongは32bitであり、約2GB(正確には2,147,483,647)までしか正しく表現できません。

これを超える大きさのファイルに対してftellを使うと、次のような問題が生じる可能性があります。

  • 負の値になってしまう
  • 桁あふれによって誤ったサイズが返される

対策としては、次のような方法があります。

  • 64bit環境(多くのLinuxなど)ではlongが64bitであることも多く、実用上問題にならない場合がある
  • 一部の処理系ではftello_ftelli64など、より大きなファイルに対応した関数が提供されている
  • POSIX環境ではoff_t型を使うAPI(fseekoftello)を利用する

ポータブルなC標準だけで巨大ファイルを安全に扱うのは難しいため、対象とするプラットフォームごとに「大きなファイル対応」のAPIを確認することが重要です。

実践的なファイルポインタ操作テクニック

ファイルの先頭・末尾へ移動する実用パターン

日常的によく使うのが、ファイルの先頭と末尾への移動です。

典型的なコードパターンをまとめます。

ファイル先頭へ移動する:

C言語
/* ファイルの先頭に移動 */
if (fseek(fp, 0L, SEEK_SET) != 0) {
    perror("fseek to beginning failed");
}

ファイル末尾へ移動する:

C言語
/* ファイルの末尾に移動 */
if (fseek(fp, 0L, SEEK_END) != 0) {
    perror("fseek to end failed");
}

末尾に移動してからftellを呼び出すと、そのままファイルサイズの取得になりますし、先頭に戻せば再度読み直しができます。

この2つのパターンを覚えておくだけでも、ファイル操作の幅が大きく広がります。

ランダムアクセス(任意位置への読み書き)の例

固定長レコードを持つバイナリファイルでは、任意のレコードへ直接ジャンプするランダムアクセスが簡単に実現できます。

次のサンプルでは、構造体Recordを配列のようにファイルへ保存し、n番目のレコードを読み込む例を示します。

C言語
#include <stdio.h>

/* 固定長のレコード構造体 */
typedef struct {
    int  id;
    char name[32];
} Record;

/* n番目(0始まり)のレコードを読み込む関数 */
int read_record(FILE *fp, long index, Record *out) {
    long offset = index * (long)sizeof(Record);

    /* 対象レコードの先頭へシーク */
    if (fseek(fp, offset, SEEK_SET) != 0) {
        perror("fseek in read_record failed");
        return -1;
    }

    /* 1レコード分読み込む */
    size_t nread = fread(out, sizeof(Record), 1, fp);
    if (nread != 1) {
        if (feof(fp)) {
            fprintf(stderr, "End of file reached while reading record %ld\n", index);
        } else {
            perror("fread in read_record failed");
        }
        return -1;
    }

    return 0;
}

int main(void) {
    const char *filename = "records.bin";
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        perror("fopen failed");
        return 1;
    }

    Record rec;
    long index = 5;  /* 6番目(0始まりで5)のレコードを読む */

    if (read_record(fp, index, &rec) == 0) {
        printf("Record %ld: id=%d, name=%s\n", index, rec.id, rec.name);
    }

    fclose(fp);
    return 0;
}

出力例(ファイル内容に依存):

実行結果
Record 5: id=123, name=Alice

このように、「index × レコードサイズ = シーク先オフセット」という考え方で、配列を扱う感覚でファイルデータにアクセスできます。

追記モードでのfseek使用時の注意点

ファイルを"a""ab"といった追記モードで開いた場合、C標準規格では次のように規定されています。

  • 書き込み操作は常にファイル末尾で行われる
  • fseekで位置を変えても、次の書き込みは末尾に対して行われる場合がある

つまり、追記モードで「途中に戻って上書きする」といった使い方は基本的にできません

この点は実装によって挙動に差が出やすいため、特に注意が必要です。

途中のデータを上書きしたい場合は、

  • 読み書きモード"r+"または"rb+"を使う
  • 必要に応じて明示的にfseekで位置を変更する

といった方法を取るべきです。

C言語
/* 読み書きモードで開き、途中のデータを書き換える例(概要のみ) */
FILE *fp = fopen("data.bin", "rb+");  /* 読み書き + バイナリ */
if (fp == NULL) {
    perror("fopen failed");
    return 1;
}

/* 100バイト目から4バイトを書き換える */
if (fseek(fp, 100L, SEEK_SET) != 0) {
    perror("fseek failed");
    fclose(fp);
    return 1;
}

int value = 42;
if (fwrite(&value, sizeof(value), 1, fp) != 1) {
    perror("fwrite failed");
}

fclose(fp);

クロスプラットフォームでの挙動差

fseekとftellはC標準ライブラリの関数ですが、実際の挙動にはOSや処理系による違いがいくつか存在します。

代表的なものを挙げます。

1つ目は改行コード変換に関する違いです。

特にWindowsでは、テキストモードで開いたファイルに対して

  • ディスク上: CRLF(2バイト)
  • プログラム側: LF(1バイト)

といった変換が行われるため、ftellが返す値が「読み込まれた文字数」と一致するものの、「ディスク上のバイト数」とは一致しないことがあります。

サイズ取得にはバイナリモード"rb"を使うのが安全です。

2つ目は大きなファイルへの対応です。

POSIX環境ではfseeko/ftelloが提供され、Windowsでは_fseeki64/_ftelli64などが用意されています。

これらはより大きなオフセット型(例: 64bit)を使うことで、巨大ファイルでも安全にシーク・位置取得ができるようになっています。

クロスプラットフォームに対応したコードを書く場合は、

  • 「テキストかバイナリか」を明確に意識してモードを選ぶ
  • 必要に応じて#ifdefなどで環境ごとのAPIを使い分ける
  • C標準だけでは不十分な箇所(巨大ファイルなど)は、対象環境のドキュメントを確認する

といった点に注意すると良いでしょう。

まとめ

fseekとftellは、C言語でファイルを扱ううえでファイルポインタ(位置)を自在に操るための基本ツールです。

fseekはoriginを基準にoffsetバイトだけ移動する関数であり、ftellは現在位置をlong型で返す関数です。

両者を組み合わせることで、ファイルサイズの取得やランダムアクセスがシンプルに実現できます。

一方で、テキストモードの改行変換や、大きなファイルでのオーバーフロー、追記モードでの挙動など、環境依存の落とし穴も存在します。

用途に応じてバイナリモードを選び、戻り値やエラーを適切にチェックしながら使うことで、堅牢で柔軟なファイル処理が可能になります。

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

URLをコピーしました!