Menu

C言語のファイル操作:fopen、fclose、1行ずつの読み込み

C言語でファイルを読み書きする方法 - fopen のモード表、NULL チェック、fprintf と fputc での書き込み、fgets・fscanf・fgetc での読み込み、定番の1行ずつのループ、そしてバイナリモードを使うべき場面。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

プログラムが計算したものは、書き出さないかぎり終了と同時に消えてしまいます。C言語のファイルインターフェースは <stdio.h> - printf と同じヘッダー - にあり、意図的に似た作りになっています。ファイルはバイトのストリームであり、画面に対して働く関数には、ストリームを引数に取る双子がいます。

ハンドルの型は FILE * です。FILE の中身を覗くことはありません。これは不透明な型(typedef 参照)で、常にそのポインタだけを持ちます。

3つのステップ

すべてのファイル操作は同じ形をしています。開く、使う、閉じる。

NULL チェックは、任意の心配性ではありません。fopen は、(読み込みモードで)ファイルが存在しない、ディレクトリが書き込み不可、パスが間違っている、プロセスのファイルハンドルが尽きた - そのいずれでも失敗し、どの場合も NULL を返します。NULL のハンドルを使うのはセグメンテーション違反です。

エラーを stdout ではなく stderr に送るのが慣習です。バッファリングされず、本来の出力とは別にリダイレクトできるからです。

fopen のモード

第2引数は短いモード文字列です。"w" が既存ファイルを黙って空にするため、これを間違えるのがこの分野で最も破壊的なミスです。

モード読み書きファイルが存在する場合存在しない場合
"r"不可先頭で開く失敗NULL を返す
"w"不可空に切り詰める作成される
"a"不可末尾にのみ書く作成される
"r+"先頭で開く失敗NULL を返す
"w+"空に切り詰める作成される
"a+"どこでも読めるが、書くのは末尾作成される

これらのいずれにも b を付ける("rb""wb""ab+")とバイナリモードになります - 詳しくは最後に。

実際のデータ損失を防ぐ2つのルール:

  • 読むつもりなら "r" を使う。 打ち間違えたファイル名に対する "r+" は安全に失敗し、"w+" は空のファイルを作るだけで失うのは時間くらいです。しかし "r" のつもりで正しいファイル名に "w" を使うと、データが破壊されます。
  • ログには "a" を使う。 ストリームの位置がどこにあっても、すべての fprintf は末尾に着地します。ログが求めるのはまさにそれです。

1つのプログラムで書いて読む

下のエディタは完全な往復を実行します - ファイルを作り、レコードを書き込み、閉じ、読み込み用に開き直して、見つけたものを表示します。

fprintffscanf は、第1引数にストリームを取る printfscanf です。書式指定子まわりはすべて同一で、name のあふれを防ぐ %31s の幅指定も含まれます。

ループの条件は == 2、つまり書式が要求する項目の数です。代わりに EOF と比較するのは古典的なバグです。おかしな行があると fscanfEOF ではなく 0 を返すので、消費できない入力の上でループが永遠に回り続けます。

fgets で1行ずつ読む

fscanf は、かっちり整形されたデータには便利です。テキストファイル - 設定、ログ、CSV、人が書いたもの全般 - では、行全体を読みましょう。次のループが定番です。

fgets が正しいデフォルトである理由:

  • バッファサイズを受け取るので、オーバーフローしません。sizeof line を渡しておけば、配列のサイズを変えても呼び出しは正しいままです。
  • ファイル終端やエラーで NULL を返すので、ループの条件としてきれいです。
  • 行がバッファに収まったときは改行を残します。これは有用で、得られたものに '\n' がなければ、その行はバッファより長く、残りがまだ待っているという意味です。strcspn(line, "\n") は改行の位置(なければ文字列長)を返すので、そこに '\0' を代入すればどちらの場合も正しく切り詰められます。

本当のファイル終端とエラーを区別するには、ループの後で問い合わせます。

if (ferror(in)) {
    fprintf(stderr, "read error\n");
} else if (feof(in)) {
    /* 正常終了 */
}

ループの条件に while (!feof(fp)) と書いてはいけませんfeof は読み取りがすでに失敗したにしか真にならないので、そのループは最後のバッファの内容を1回余分に処理します。代わりに読み取り関数の戻り値をテストしましょう - 上の2つのループがそうしているように。

1文字ずつ:fgetc と fputc

バイト単位の仕事 - 文字数を数える、ファイルを変換する、コピーする - には、1呼び出しで1文字を扱う fgetcfputc があります。

重要な細部が1つ。cchar ではなく int として宣言されています。fgetcint を返すのは、あらゆるバイト値に加えて別個の番兵 EOF-1)を返せるようにするためです。これを char に格納すると、プラットフォームで char が符号付きかどうかによって、EOF との比較が常に偽になるか、バイト 0xFF に対して誤って真になります。これはC言語で最も古い落とし穴の1つです。

エラーを正しくチェックする

実運用での読み込みはこうなります。

errno は直近の失敗を表すコードを保持し、strerror はそれを文になおします。perror は自分のメッセージとその文を1回の呼び出しで表示します。両者にはそれぞれ <errno.h><string.h> が必要です。

fclose も失敗しうる - バッファされたデータをフラッシュするので、書き込みがディスクに収まらないことがある - ため、重要なものについてはチェックしましょう。

if (fclose(fp) != 0) {
    fprintf(stderr, "failed to flush and close\n");
}

バイナリモードを1段落で

テキストモードは改行を変換することがあり(Windowsでは書き込み時に \n\r\n になり、読み込み時に戻ります)、特定のバイトを特別扱いすることもあります。テキストでないデータ - 画像、そのままダンプした構造体、圧縮されたかたまり - は b を付けて開き、生のバイトを移動させる fread/fwrite を使いましょう。

fwrite(ptr, size, count, fp)size バイトの項目を count 個書き込み、実際に書けた個数を返します。fread はその鏡写しです。ただしこの方法で書かれたファイルは、書いたマシンに縛られている点に注意してください - 構造体のパディング、整数のサイズ、バイト順がすべてバイト列に漏れ出します - ので、キャッシュや一時ファイルには適していますが、ほかのプログラムが読む必要のあるフォーマットには不適切です。

よくある間違い

  • fopenNULL をチェックしない。 その後のクラッシュは、オープンではなく読み込みのせいにされます。
  • "r" のつもりで "w" で開く。 気づく前にファイルが空になっています。
  • fclose を忘れる。 バッファされた出力が失われ、閉じられないファイルごとにハンドルが漏れます。
  • while (!feof(fp)) 最後の行を2回処理します。代わりに読み取りの呼び出しをテストしましょう。
  • char c = fgetc(fp) EOF との比較が壊れます。int を使いましょう。
  • 幅なしの fscanf("%s", buf) キーボードに対する scanf とまったく同じバッファオーバーフローです。
  • 相対パス。 fopen("data.txt", "r") は実行ファイルの隣ではなく、カレントディレクトリを探します。ファイルが「消えた」ときは、たいていこれが原因です。

よくある質問

C言語でファイルを開くには?

FILE *fp = fopen("data.txt", "r"); は読み込み用にファイルを開き、FILE * のハンドルを返します。失敗した場合は NULL です。ハンドルを使う前に必ず NULL をチェックし、終わったら fclose(fp) を呼びましょう。

C言語の fopen のモードにはどんなものがありますか?

"r" は読み込み(ファイルが存在する必要あり)、"w" は書き込み(作成、または既存ファイルを空に切り詰める)、"a" は追記(作成し、末尾に書く)。+ を足すとそれぞれ読み書き両用になります:"r+""w+""a+"b を足す("rb""wb")とバイナリモードで開きます。

C言語でファイルを1行ずつ読むには?

while ループの中で fgets を使います:while (fgets(line, sizeof line, fp) != NULL) { ... }。改行ごと、またはバッファが一杯になった時点で止まり、ファイル終端で NULL を返します。バッファサイズを渡すのでオーバーフローしません。

C言語で書き込んだのにファイルが空なのはなぜですか?

たいていは fclose を忘れています。出力はバッファリングされるので、プログラムが異常終了するとデータがまだメモリに残っていることがあります。fclose はフラッシュして閉じ、fflush(fp) は閉じずにフラッシュします。もう1つの原因は、2度目に "w" で開いてしまい、今書いたファイルを切り詰めてしまうことです。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める