3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Linux][C言語]ファイル入出力をやってみる

Last updated at Posted at 2021-06-18

Linux環境のC言語でファイルの入出力を試してみました。

本記事では、ファイルの開き方、読み込み方、書き込み方、閉じ方を一通り説明します。

変更履歴

  • 2021 / 06 / 25 タイトルを修正しました。参考文献のリンクを修正しました。
  • 2021 / 06 / 21 概要を追加しました。セクション名を一部変更しました。誤字を修正しました。

概要

  • ファイルを開くには fopen 関数を使います。
  • ファイルを1バイトずつ読み込むには fgetc 関数を使います。
  • ファイルに1バイトずつ書き込むには fputc 関数を使います。
  • ファイルを閉じるには fclose 関数を使います。
  • 標準ライブラリでの入出力で FILE 構造体はファイルを表す中心的な存在です。
  • 戻り値のみでエラーか判断できない場合は ferror 関数を使いエラーが起きたのかを判断します。

動機

ファイルに関する操作を理解することでLinuxへの理解を深められるのではないかと思い調査と記事を書きました。

Linuxではファイルが重要な役割を持っていて、データだけではなく、デバイスやネットワーク越しのリソースなどをファイルとして抽象化します。つまり、ファイルの操作を理解することはLinuxへの理解にも繋がると思います。思えば大学生のときにLinuxの授業で初めてやったのもファイル入出力でした。(ちなみに、その時の記憶はほとんど忘れてます :joy:

前提知識

下記に挙げた内容に関して詳細な説明は省略します。

  • C言語の基本的な知識(条件分岐やポインタなど)
  • C言語で書かれたプログラムをコンパイルし実行できる

動作環境

この記事を書くにあたり使用した環境は以下の通りです。

  • コンパイラ:gcc(バージョン9.3.0)
  • OS:Ubuntu(バージョン20.04.2 LTS)

作成したプログラム

最初にファイル入出力の全体像を掴んでもらうためにサンプルプログラムを載せます。詳細な説明はこのあとに説明します。このサンプルプログラムは引数で与えられたファイルを開き、最初の行を読み込み、 output.txt というファイル名で読み込んだ行を書き込み、最後にファイルを閉じます。なお、このプログラムでは簡略化のため最大で256バイト読み込むようにしています。

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

# define BUFFER_SIZE 256

static char buffer[BUFFER_SIZE];
static int buffer_index = 0;

int main(int argc, char *argv[]) {
  // 1. ファイルを開く。
  FILE *fr = fopen(argv[1], "r");

  // NULLが返却された場合、ファイルを開くことに失敗しているので、
  // プログラムを異常終了する。
  if (!fr) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

  // 2. ファイルの先頭一行を読み込む。
  int c;
  while ((c = fgetc(fr)) != EOF) {
    if (buffer_index >= BUFFER_SIZE) break;
    if (c == '\n') break;
    buffer[buffer_index] = c;
    buffer_index++;
  }

  // 3. ファイルを閉じる。
  //    EOFが返った場合ファイルを閉じることに失敗しているので、
  //    プログラムを異常終了する。
  if (fclose(fr) == EOF) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

  // 読み込みに失敗した場合、プログラムを異常終了する。
  if (ferror(fr)) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

  // 4. 書き込み用のファイルを開く。
  FILE *fw = fopen("output.txt", "w");

  if (!fw) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

  // 5. 書き込む
  for (int j = 0; j < buffer_index; j++) {
    if (fputc(buffer[j], fw) == EOF) {
      // 書き込みに失敗した場合、プログラムを異常終了する。
      perror(argv[0]);
      exit(EXIT_FAILURE);
    }
  }

  if (fclose(fw) == EOF) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

  exit(EXIT_SUCCESS);
}

このプログラムで使用した標準ライブラリの関数は次の4つです。

  • fopen: ファイルを開く
  • fclose: ファイルを閉じる
  • fgetc: ファイルから1バイト読み込む
  • fputc: ファイルに1バイト書き込む

以降で順にこれらの関数について説明します。

ファイルを開く・閉じる

ファイルを開くには fopen を、閉じるには fclose を使います。それぞれの定義は次のとおりです。

FILE *fopen(const char *pathname, const char *mode);

int fclose(FILE *stream);  

どちらの関数も FILE という型を使っています。 FILE はファイルを表す構造体です。この構造体の中にはファイルディスクリプタ1と言われる、ファイルを表す番号が含まれています。プログラムを書く側から隠蔽されていますが、標準ライブラリでは内部でファイルディスクリプタを使ってカーネルとやり取りをしていることになるでしょう。ただ、標準ライブラリを使って入出力をする際にはこのあたりを意識せずに使用できます。

fopen では pathname で開くファイルのパスを指定し、 mode でモードを指定します。モードとはファイルをどういった用途で使用するかを示すもので、関数の挙動に影響します。よく使われるであろうモードは次のとおりです。

モード 意味
r 読み込み専用で開く。読み込み開始位置は先頭にセットされる。
w 書き込み専用で開く。ファイルが存在する場合は中身を空にした上で書き込む。ファイルが存在しない場合は新たに作成する。書き込み開始位置は先頭にセットされる。
a 追加専用で開く。ファイルが存在しない場合は新たに作成する。書き込み開始位置は最後にセットされる。

ここで、位置という言葉が出てきました。位置とは、例えるなら、テキストエディタのカーソルのようなものです。このあと説明する fgetcfputc などの関数は、この位置を起点としてファイルの読み書きを行います。ファイルはそれぞれ内部で位置を状態として持っており、位置を変更することでファイルの読み込みや書き込みの場所を変更できます。 (ここでは詳しく解説しませんが、 fseek 関数で位置を明示的に指定できます。)

fclose では stream で指定したファイルを閉じます。基本的には fopen で開いた FILE を引数に渡すことになると思います。ベストプラクティスとして、よく使い終わったファイルはなるべく早く閉じろ、というものがあります。これはファイルというリソースはさまざまなプロセス間で共有されるものなので、一つのプログラムがいつまでも専有するべきではないからです。使い終わったら早めに fclose を呼び出すといいでしょう。

上記のプログラムでは読み込むファイル、書き込むファイルを開くときにこれらの関数を使用しました。該当する箇所は以下です。

読み込むファイルを開く・閉じる
  // 1. ファイルを開く。
  FILE *fr = fopen(argv[1], "r");

  // NULLが返却された場合、ファイルを開くことに失敗しているので、
  // プログラムを異常終了する。
  if (!fr) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

  // ~ 中略 ~

  // 3. ファイルを閉じる。
  //    EOFが返った場合ファイルを閉じることに失敗しているので、
  //    プログラムを異常終了する。
  if (fclose(fr) == EOF) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }
書き込むファイルを開く
  // 4. 書き込み用のファイルを開く。
  FILE *fw = fopen("output.txt", "w");

  if (!fw) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

  // ~ 中略 ~

  if (fclose(fw) == EOF) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }

上記のプログラムでは戻り値を確認して、処理がうまく行かない場合プログラムを異常終了させています。 fopen はエラーが起きると NULL を返します。 fclose はエラーが起きると EOF を返します。それぞれエラーが起きた際には perror を使ってエラー内容を表示させています。

関数の詳細については次のページで確認できます。

読み込み・書き込み

ファイルを開くとファイルの読み込み・書き込みができるようになります。 fgetc は読み込みができ、 fputc は書き込みができます。どちらも1バイトづつ処理します。それぞれの関数の定義は次のとおりです。

int fgetc(FILE *stream);

int fputc(int c, FILE *stream);

それぞれ引数で FILE 構造体を受け取るようになっていることがわかります。 fopen で開いたファイルを引数で指定できるということです。

また、それぞれ戻り値が int 型になっています。値は読み込み、書き込みした文字になります。なぜ char 型ではないのかというとエラーが起きた際に EOF が返されるからです。 EOF-1int 型の値なので、戻り値が int 型になっているというわけです。

書き込みを行う fputc 関数は、第一引数で書き込む文字を int 型で受け取るようになっています。こちらは関数内部で unsigned char にキャストされ、書き込まれます。

上記のプログラムではこれらの関数を次の箇所で使用しています。

読み込み
  // 2. ファイルの先頭一行を読み込む。
  int c;
  while ((c = fgetc(fr)) != EOF) {
    if (buffer_index >= BUFFER_SIZE) break;
    if (c == '\n') break;
    buffer[buffer_index] = c;
    buffer_index++;
  }

  // 〜中略〜

  // 読み込みに失敗した場合、プログラムを異常終了する。
  if (ferror(fr)) {
    perror(argv[0]);
    exit(EXIT_FAILURE);
  }
書き込み
  // 5. 書き込む
  for (int j = 0; j < buffer_index; j++) {
    if (fputc(buffer[j], fw) == EOF) {
      // 書き込みに失敗した場合、プログラムを異常終了する。
      perror(argv[0]);
      exit(EXIT_FAILURE);
    }
  }

それぞれ戻り値を確認し、エラーが起きていればプログラムを異常終了します。 fputc はエラーが発生すると EOF を返します。 fgetc はファイルの最後に到達した場合時 EOF を返しますが、エラーが発生したときも EOF を返します。これでは戻り値を見ただけで判断できないので、 ferror を使用して FILE にエラーが発生したか確認しています。このように、戻り値だけではファイルの最後に達したのか、エラーが発生したのか、判断できない関数は他にもあり、その場合は ferror を使って判断するのが基本のようです。

関数の詳細については次のページで確認できます。

参考文献

  1. 書籍: ふつうのLinuxプログラミング第2版 青木 峰郎著
  2. Webサイト: 苦しんで覚えるC言語 (https://9cguide.appspot.com/
  3. Webサイト: JM Project - Linux関連のマニュアルページの日本語版 (http://linuxjm.osdn.jp/
  1. ファイルディスクリプタについてはこちらに説明があります。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?