C言語入門|ランダムアクセスの基本

これまで、ファイルを先頭から順番に読み書きする方法を学んできました。
しかし実際のプログラムでは、

  • ファイルの途中にあるデータだけを読みたい。
  • 決まった位置にあるレコードを直接更新したい。

といった場面もよくあります。

このように、ファイルの任意の位置に直接アクセスする方法
ランダムアクセス(random access)といいます。

これに対して、先頭から順に読み書きする方法は
シーケンシャルアクセス(sequential access)と呼ばれます。

シーケンシャルアクセスとランダムアクセスの違い

まずは2つの違いを整理しておきましょう。

アクセス方法特徴
シーケンシャルアクセス先頭から順番に読み書きする。
ランダムアクセスファイルの任意の位置に直接移動して読み書きする。

ランダムアクセスは自由度が高く便利ですが、
必ずしも万能ではないという点が重要です。

なぜ基本はシーケンシャルアクセスなのか

ランダムアクセスが便利なのに、
なぜ「基本はシーケンシャル」と言われるのでしょうか。

理由の1つは、記憶装置の仕組みにあります。

従来よく使われてきたハードディスクドライブ(HDD)は、

  • 円盤(磁気ディスク)が回転し
  • 読み取りヘッド(針)が移動してデータを探す

という構造になっています。

読み書きしたい場所へ針を動かすことを シーク(seek)
その移動にかかる時間を シークタイム(seek time) といいます。

ランダムアクセスを多用すると、

  • 針の移動が頻繁になる。
  • シークタイムが増える。
  • 全体の処理速度が低下する。

という問題が起こりやすくなります。

最近主流の SSD は物理的な針を持たないため影響は小さいですが、
それでも 不要なランダムアクセスは避けるのが基本です。

また、バックアップ用のテープ装置などでは
そもそもランダムアクセスができない、または非常に遅い場合もあります。

C言語におけるランダムアクセスの考え方

C言語では、ランダムアクセスを行うために

  • 読み書き位置を移動する。
  • 現在の位置を取得する。

という仕組みが用意されています。

ただし、テキストモードでのランダムアクセスは注意点が多いため、
本記事では バイナリモードでの利用を前提として解説します。

fseek ― 読み書き位置を移動する

ファイルの読み書き位置を変更するには、fseek 関数を使います。

書式

int fseek(FILE* fp, long offset, int origin);

引数の意味

引数説明
fpファイルポインタ
offset基準位置から移動するバイト数
origin基準位置

基準位置には、次の定数を指定します。

定数基準となる位置
SEEK_SETファイルの先頭
SEEK_CUR現在の位置
SEEK_ENDファイルの終端

使用例

fseek(fp, 9L, SEEK_SET);

この場合、
ファイルの先頭から9バイト目に読み書き位置が移動します。

ftell ― 現在の読み書き位置を取得する

現在の読み書き位置を知りたい場合は、ftell 関数を使います。

書式

long ftell(FILE* fp);

戻り値

  • ファイル先頭からのバイト数
  • 失敗時は -1L

使用例

long pos = ftell(fp);

これで、現在の読み書き位置を取得できます。

fseek と ftell を組み合わせた考え方

fseek と ftell は、組み合わせて使うことで柔軟な制御ができます。

たとえば、

fseek(fp, ftell(fp) - 16L, SEEK_SET);

このコードは、

  • 現在の位置を取得
  • そこから16バイト前に戻る

という意味になります。

サンプルプログラム:指定位置の int 型データを読み取る

次のプログラムは、

  • int 型データを3個バイナリで書き込み
  • 2番目のデータだけをランダムアクセスで読み取る

例です。

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

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

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

int main(void)
{
    FILE* fp;
    int data[3] = { 10, 20, 30 };
    int value;

    // 書き込み
    if ((fp = fopen("data.dat", "wb")) == NULL) {
        exit(1);
    }
    fwrite(data, sizeof(int), 3, fp);
    fclose(fp);

    // 読み取り
    if ((fp = fopen("data.dat", "rb")) == NULL) {
        exit(1);
    }

    // 2番目のint型へ移動
    fseek(fp, sizeof(int), SEEK_SET);
    fread(&value, sizeof(int), 1, fp);
    fclose(fp);

    printf("読み取った値:%d\n", value);
    return 0;
}

実行結果

読み取った値:20

このように、
固定サイズのデータを扱う場合、ランダムアクセスは非常に相性が良い
ということがわかります。

まとめ

  • ランダムアクセスはファイルの任意位置に直接アクセスできる。
  • 基本はシーケンシャルアクセスだが、必要な場面では非常に便利
  • fseek で位置を移動し、ftell で現在位置を取得する。
  • バイナリモードでの利用が安全でおすすめ。