前回から2回にわたり,C言語によるファイル処理を説明しています。前回は,テキスト・ファイルにシーケンシャルにアクセスして,先頭から順に読み書きする方法を解説しました。バッファを使ったストリーム入出力で効率良くファイルにアクセスできることや,キーボードやディスプレイなどの周辺機器も標準入出力としてファイルのように扱えることを説明しましたね。2回目となる今回は,ランダム・アクセスとバイナリ・ファイルの操作について説明します。まずは,ランダム・アクセスから解説を始めましょう。

 ランダム・アクセスはファイルの任意の位置からデータを読み書きする方法で,ハードディスクやCD-ROMのアクセスに似ています。

 一つのデータの長さが決まっている固定長のファイルなら,ファイルの中の関連するデータの固まり(レコード)に,どこの位置からでもアクセスすることができて便利です。シーケンシャルにファイルを読み込む場合,ファイルをオープンした時点でのファイル・アクセスの基点はファイルの先頭です。データを読み込んでいくと現在位置もそれに合わせて変わっていきます。ですから,一文字入力を行うfgetc関数でファイルを読み込んだときや一行入力を行うfgets関数で読み込んだときも,特別な操作なしに順々にデータを読み込んでいくことができたわけです。

 これに対し,任意の位置から読み書きを行うには,ファイル内の現在位置を移動させる必要があります。この現在位置のことを,一般的にファイル・ポインタやファイル・ポジションと呼びます。ただ,ファイル・ポインタというと,fopen関数が返すFILE構造体へのポインタと紛らわしいので,ここでは「ファイル位置指示子」という名前で呼ぶことにしましょう。ハードディスクのどこにそのファイルが存在するかを指示するような感じを持たれるかもしれませんが,ファイルのどこがカレントな位置(現在位置)なのかを示すのがファイル位置指示子であると理解してください。

アクセス・カウンタを作ってみよう

 まずはサンプルとして,Webサイトの訪問者数を記録するアクセス・カウンタを作ってみましょう。count.txtというテキスト・ファイルに訪問数を記録するプログラムです(リスト1)。特に問題はなさそうですね。count.txtには,図1のように0を六つ並べて「000000」と書き込んでおきます。メモ帳などのテキスト・エディタを使うといいでしょう。

リスト1●アクセス・カウンタのプログラム
リスト1●アクセス・カウンタのプログラム

図1●テキスト・ファイルcount.txtに,000000と書き込んでおく。最後に改行しないこと
図1●テキスト・ファイルcount.txtに,000000と書き込んでおく。最後に改行しないこと

 リスト1のプログラムをコンパイルして1回だけ実行すると,確かに「000001」とカウントアップされます。しかしその後,何回このプログラムを実行しても,「000001」のままです(図2)。まったくカウントアップしません。テキスト・エディタであらためてcount.txtを開いて見ると,000000の後に000001と書き込まれているのがわかります(図3)。なぜ,こうなってしまうのでしょう?

図2●リスト1を何回実行しても,「000001」としかならない
図2●リスト1を何回実行しても,「000001」としかならない

図3●count.txt内が想定どおりになっていない
図3●count.txt内が想定どおりになっていない

 ソースコードを追って考えていきましょう。リスト1をよく見てください。まず(1)で,ファイルcount.txtを読み書き両用モード(r+)でファイルを開いています。読み書き両用のモードは,表1のように3種類あります。(2)では,count.txtから現在の値を読み出しています。最初は000000を読み込んでlong型の変数iに代入していますね。それを(3)のi++で1加算して,(4)のfprintf関数で6桁の000001とファイルに書き込んでいるはずです。なのに,なぜ000000が000001と上書きされずに「00000000001」となったのでしょう?

表1●読み書き両用の場合のファイルのオープン・モード
表1●読み書き両用の場合のファイルのオープン・モード

 その理由は,前述のファイル位置指示子にあります。最初,ファイルを開いたときのファイル位置指示子は,「000000」の先頭にあります。しかし,(2)のfscanf関数を実行すると,ファイル位置指示子は図4のように移動してしまうのです。すると(4)のfprintf関数は,ファイル位置指示子の指す位置から6桁分を書き込み,000000の後に000001と追記することになります。その結果,次にcount.txtファイルをオープンしても,最初の6桁である「000000」の部分しか読み込まないので,いつまでたってもカウントアップしないわけです。

図4●fopen関数を実行するとファイル位置指示子は移動してしまう
図4●fopen関数を実行するとファイル位置指示子は移動してしまう

ftell関数で現在位置を知りrewind関数で元に戻る

 ファイル位置指示子がどこを指しているのかという情報は,ftell関数を使うと知ることができます。ftellは,引数としてファイル・ポインタ(FILE構造体へのポインタ)を受け取り,現在位置をlong型で返す関数です。例えばリスト2ではftell関数を使って,読み込み前後/書き込み前後の現在位置を表示させています(図5)。

リスト2●ftell関数を使ったプログラム
リスト2●ftell関数を使ったプログラム
[画像のクリックで拡大表示]

図5●リスト2を実行したところ
図5●リスト2を実行したところ

 ファイル・ポインタを操作する関数は,ftell関数のほかにrewindとfseek(後述)があります(表2)。まず,rewind関数から見てみましょう。リスト2では,ファイル・オープン直後(読み込み前)の現在位置は0,fscanf関数で読み込み後の値は6です。(1)のrewind関数は,ファイル位置指示子をファイルの先頭に戻します。要するにリワインド:巻き戻しですね。つまり(2)で,ファイル位置指示子の現在位置は0になります。

表2●ファイル位置指示子を操作する三つの関数
表2●ファイル位置指示子を操作する三つの関数

 実際にリスト2のプログラムをコンパイルして実行してみてください。プログラムを繰り返し実行すると,カウンタの数が一つずつアップしていくのがわかると思います。ファイル位置指示子を移動させることで,カウント・アップした値を上書きすることができたわけです*1