このページの解説は C99 をベースとしています。
以下は目次です。
データを、先頭から順番に順序どおりに読み書きしていくようなアクセス方法のことを、シーケンシャルアクセス、順次アクセス (sequential access) と呼びます。これに対して、任意の位置に自由に移動しながら読み書きをおこなう方法を、ランダムアクセス、直接アクセス (random access) と呼びます。
前章で、いろいろな関数を使ってファイルの読み書きを行いましたが、その際にファイルポジションという概念が登場しました。ファイルポジションは、読み書きをおこなうファイル内の位置のことで、読み書きをおこなうと自動的に動きます。ファイルを開いた直後、ファイルポジションはファイルの先頭にあり、読み書きをおこなうことで移動していきます。これはシーケンシャルアクセスをしているということです。
読み書きをおこなう以外にも、ファイルポジションを移動させる方法があります。このような操作を一般に、シーク (seek) と呼び、シークをおこなうための標準ライブラリ関数がいくつかあります。シークをおこなうことで、ランダムアクセスが実現できます。
シークをおこなうために用いる代表的な関数が、第40章でも登場した fseek関数です。fseek関数は、<stdio.h> に次のように宣言されています。
int fseek(FILE* stream, long int offset, int origin);
第1引数は FILEオブジェクトへのポインタ、第2引数にファイルポジションの移動量をバイト単位で指定します。第3引数は、シークの基準となる原点位置を指定します(詳細は後述します)。戻り値は、成功すれば 0 を、失敗すると 0以外の値を返します。
fseek関数には制限が多くあります。また、テキストファイルとして開いているのか、バイナリファイルとして開いているのかによっても制限事項は異なっており、少々複雑です。バイナリファイルの場合については、第42章であらためて説明するとして、ここではテキストファイルの場合に限った話をします。
テキストファイルの場合にできることは、以下の4つのいずれかです。
これ以外のこともできる可能性はありますが、C言語の規格上は保証されません。
第3引数に指定する SEEK_SET、SEEK_CUR、SEEK_END はそれぞれ、stdio.h で定義されているオブジェクト形式マクロです。それぞれ、「ファイルの先頭」「現在のファイルポジション」「ファイルの末尾」を意味しています。第3引数に指定できるのは、これら3つのいずれかだけです。
また、ftell関数は、現在のファイルポジションを返す関数です。<stdio.h> に次のように宣言されています。
long int ftell(FILE* stream);
ftell関数の戻り値は、バイナリファイルの場合には、ファイルの先頭からのバイト数であると決まっていますが、テキストファイルの場合には、どんな数値であるか未規定📘です。テキストファイルに対する ftell関数が返した値は、先ほどの fseek関数の4番目の使い方以外には用いられません。なお、失敗した場合は、-1L が返されます。
さきほどの4項目をよく観察すると、テキストファイルにおけるファイルポジションの移動は、あまり自由度がないことが分かります。4番目の使い方は、現在の位置を ftell関数に問い合わせて、その戻り値を変数に保存しておけば、その後、読み書きをおこなうなり、先頭や末尾へ移動するなりした後、再び元の位置に戻ってくることができるということです。
使用例を挙げます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void put_file_line(FILE* fp);
int main(void)
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL) {
("ファイルオープンに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
(fp); // 1行目
put_file_linelong pos = ftell(fp); // ファイルポジションを保存
if (pos == -1L) {
("ファイルポジションの取得に失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
(fp); // 2行目
put_file_line(fp); // 3行目
put_file_lineif (fseek(fp, pos, SEEK_SET) != 0) { // 保存しておいた位置へ復帰
("ファイルポジションの移動に失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
(fp); // 2行目
put_file_line
if (fclose(fp) == EOF) {
("ファイルクローズに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
}
/*
ファイルから1行読み取って、標準出力へ出力。
引数
fp: FILEオブジェクトへのポインタ。
*/
static void put_file_line(FILE* fp)
{
char buf[80];
if (fgets(buf, sizeof(buf), fp) == NULL) {
("ファイルからの読み込みに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
// 末尾の改行文字を取り除く
char* p = strchr(buf, '\n');
if (p != NULL) {
*p = '\0';
}
(buf);
puts}
入力ファイル(test.txt)
aaaaa
bbbbb
ccccc
実行結果(標準出力):
aaaaa
bbbbb
ccccc
bbbbb
fseek関数や ftell関数の引数や戻り値の型は long int型です。long型が 32ビットの環境であれば、この最大値は 2,147,483,647 です(LONG_MAXマクロの置換結果)。これは、ファイルサイズとして考えると、約2GB ということです。
2GB というと、たとえば DVD1枚のデータ量の半分以下ということですから、万が一、巨大なデータを扱うことがあるとすれば、少々心もとない大きさかもしれません。
より巨大なファイルを扱うには、fgetpos関数や fsetpos関数が利用できます。これらの関数は、<stdio.h> に以下のように宣言されています。
int fgetpos(FILE* restrict fp, fpos_t* restrict pos);
int fsetpos(FILE* fp, const fpos_t* pos);
restrict については、第57章で取り上げます。動作に影響はないので、今は無視して問題ありません。
fgetpos関数は、第2引数に fpos_t型のポインタを指定します。ファイルポジションはこのポインタを通して格納されます。戻り値は、成功したときに 0、失敗したとき 0以外です。
fsetpos関数は、第2引数にやはり fpos_t型の constポインタを指定します。このポインタが指す先の値を、ファイルポジションに設定します。ここで指定できる値は、fgetpos関数から得た値でなければなりません。戻り値は、成功したときに 0、失敗したとき 0以外です。
fpos_t型がどのような型であるかは処理系定義📘ですが、通常 long int型よりも大きく定義されており、必要十分な大きさがあると考えられます。
前の項の fseek関数と ftell関数のサンプルプログラムを、fgetpos関数と fsetpos関数で書き換えてみます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void put_file_line(FILE* fp);
int main(void)
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL) {
("ファイルオープンに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
fpos_t pos;
(fp); // 1行目
put_file_lineif (fgetpos(fp, &pos) != 0) { // ファイルポジションを保存
("ファイルポジションの取得に失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
(fp); // 2行目
put_file_line(fp); // 3行目
put_file_lineif (fsetpos(fp, &pos) != 0) { // 保存しておいた位置へ復帰
("ファイルポジションの移動に失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
(fp); // 2行目
put_file_line
if (fclose(fp) == EOF) {
("ファイルクローズに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
}
/*
ファイルから1行読み取って、標準出力へ出力。
引数
fp: FILEオブジェクトへのポインタ。
*/
static void put_file_line(FILE* fp)
{
char buf[80];
if (fgets(buf, sizeof(buf), fp) == NULL) {
("ファイルからの読み込みに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
// 末尾の改行文字を取り除く
char* p = strchr(buf, '\n');
if (p != NULL) {
*p = '\0';
}
(buf);
puts}
入力ファイル(test.txt)
1行目
2行目
3行目
4行目
5行目
実行結果(標準出力):
1行目
2行目
3行目
2行目
ここでは、追記をやってみましょう。これはつまり、既存のファイルの末尾に文字を書き足すような処理です。
オープンモードを “w” や “w+” にすると、ファイルを開いた時点で中身を失ってしまうので、追記できません。“r” は読み取り専用なので、書き込むことができません。
1つの方法は、“r+” で開いて、fseek関数を使ってファイルの終わりまで移動し、書き込みをおこなうことです。たとえば、次のようになります。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp = fopen("test.txt", "r+");
if (fp == NULL) {
("ファイルオープンに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
if (fseek(fp, 0L, SEEK_END) != 0) {
("ファイルポジションの移動に失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
if (fputs("xyz", fp) == EOF) {
("ファイルへの書き込みに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
if (fclose(fp) == EOF) {
("ファイルクローズに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
}
実行前のファイル(test.txt)
aaa
bbb
ccc
実行後のファイル(test.txt)
aaa
bbb
ccc
xyz
実行結果(標準出力):
“r+” というオープンモードは、読み取りを行った後、書き込みも行えるというような意味合いです。まずは読み取りということなので、“r” と同様に、ファイルが存在しない場合には失敗します。
これでもいいのですが、“a” というオープンモードを使えば、より直接的に追記が実現できます。“a” は基本的に “w” と同じく書き込み専用のオープンモードですが、ファイルの中身を失いません。また、ファイルが開かれた時点で、ファイルポジションがファイルの終わりにあります。
なお、追記しつつも、読み取りも行いたいのであれば、“a+” を使います。
ややこしいので、各オープンモードの動作を表にまとめておきます。
操作 | r | w | a | r+ | w+ | a+ |
---|---|---|---|---|---|---|
読み取り | できる | できない | できない | できる | できる | できる |
書き込み | できない | できる | できる | できる | できる | できる |
開くとファイルの中身は… | そのまま | 失われる | そのまま | そのまま | 失われる | そのまま |
開くとファイルポジションは… | 先頭にある | 先頭にある | 終わりにある | 先頭にある | 先頭にある | 終わりにある |
ファイルが存在しないときに開こうとすると… | 失敗する | 空のファイルが作られる | 空のファイルが作られる | 失敗する | 空のファイルが作られる | 空のファイルが作られる |
このように、同じものがないことが分かります。なお、オープンモードに、バイナリファイルとして開くことを表す “b” が付いていても、これらの動作には変わりはありません。
さて、先ほどのサンプルプログラムを、“a” を使って書き換えてみます。今度は fseek関数が必要ありません。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp = fopen("test.txt", "a");
if (fp == NULL) {
("ファイルオープンに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
if (fputs("xyz", fp) == EOF) {
("ファイルへの書き込みに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
if (fclose(fp) == EOF) {
("ファイルクローズに失敗しました。\n", stderr);
fputs(EXIT_FAILURE);
exit}
}
実行前のファイル(test.txt)
aaa
bbb
ccc
実行後のファイル(test.txt)
aaa
bbb
ccc
xyz
実行結果(標準出力):
問題① オープンモードが “w” の場合と、“a” の場合との挙動の違いを確かめてください。
問題② オープンモードの “a+” を使い、ファイルへの追記を行った後、ファイル全体を標準出力へ出力するプログラムを作成してください。
()
の前後の空白の空け方)(
の直後、)
の直前に空白を入れない)return 0;
を削除(C言語編全体でのコードの統一)
Programming Place Plus のトップページへ
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
![]() |
管理者情報 | プライバシーポリシー |