ファイル入出力

C言語のファイル入出力の入門的な解説です。ファイル入出力の方法には、一文字づつを読み書きする方法や、数バイトづつ読み書きする方法、ファイル全体を読み書きする方法を紹介します。

ファイルをオープンする

ファイル入出力するためには、まず最初にファイルをオープンする必要があります。

fopen関数を使うと、ファイルを開くことができます。stdio.hヘッダを読み込むと使えます。

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

第一引数は、開きたいファイルのファイル名です。第二引数は、モードです。ファイルオープンに成功すると、ファイルストリームを表現する「FILE*型」の値が返されます。

モードの一覧

モードの一覧です。

モード動作
r読み込み(ファイルがないときはエラー)
w書き込み(ファイルがないときは新規作成)
a追加書き込み(ファイルがないときは新規作成)
r+読み込みと書き込み(ファイルがないときはエラー)
w+読み込みと書き込み(ファイルがないときは新規作成)
a+読み込みと追加書き込み(ファイルがないときは新規作成)

書き込みは、ファイルの内容がクリアされ、新しい内容で書き込むことを意味します。追加書き込みは、ファイルの末尾に追加して、書き込むことを意味します。

上記のモードはバイナリモードを意味する「b」と組み合わせることができます。

br
bw

デフォルトの動作は、テキストモードオープンされ、Windowsの場合にはファイルに含まれる改行文字である「CR」「LF」の並びが「\n」に置き換えられます。

バイナリモードでは、このような置き換えはされません。

ファイルをクローズする

ファイル入出力が終わったら、ファイルを閉じる必要があります。

fclose関数でファイルを閉じることができます。stdio.hヘッダを読み込むと使えます。

int fclose(FILE* fp);

引数は閉じたいファイルのファイルストリームです。ファイルクローズが、成功した場合は0、エラーの場合はEOFを返します。ファイルをクローズすると、バッファは、フラッシュされます。バッファとは、書き込みデータを一時的に保存している、メモリ領域のことです。フラッシュとは、バッファのデータを、書き出すことを言います。

ファイルから一文字読み込む

ファイルから一文字読み込むには、fgetc関数を使います。

fgetc関数は、ファイルから1文字読み込む関数です。stdio.hヘッダを読み込むと使えます。

#include <stdio.h>
int fgetc(FILE *fp);

ファイルストリームから1文字読み込み、読み込んだ文字を返します。ファイルの最後に到達した場合は、EOFが返ります。

ファイルから一文字づつ読み込む

ファイルから1文字づつ読み込むサンプルです。fgetc関数のサンプルです。fopen関数で読み込みモードでファイルを開き、fgetc関数で1文字づつ読み込み、標準出力に出力し、fclose関数でファイルを閉じています。

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

int main(void) {
  
  // ファイルを読み込みモードで開く
  const char* in_file = "input.txt";
  FILE* in_fp = fopen(in_file, "r");
  if (in_fp == NULL) {
    fprintf(stderr, "Can't open file %s\n", in_file);
    exit(1);
  }
  
  // 読み込んで、標準出力に出力
  int32_t ch;
  while(ch = fgetc(in_fp)) {
    // EOFが返ってきた場合
    if (ch == EOF) {
      // ファイルの末尾かチェックする
      if (feof(in_fp)) {
        break;
      }
    }
    fputc(ch, stdout);
  }
  
  // fclose関数でファイルを閉じる
  fclose(in_fp);
}

入力ファイル「input.txt」です。

Hello
World!

出力結果です。

Hello
World!

ファイルに書き出す

上記のサンプルの出力先を「output.txt」にしてみましょう。

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

int main(void) {
  
  // ファイルを読み込みモードで開く
  const char* in_file = "input.txt";
  FILE* in_fp = fopen(in_file, "r");
  if (in_fp == NULL) {
    fprintf(stderr, "Can't open file %s\n", in_file);
    exit(1);
  }

  // ファイルを書き込みモードで開く
  const char* out_file = "output.txt";
  FILE* out_fp = fopen(out_file, "w");
  if (out_fp == NULL) {
    fprintf(stderr, "Can't open file %s\n", out_file);
    exit(1);
  }
  
  // 読み込んで、ファイルにに出力
  int32_t ch;
  while(ch = fgetc(in_fp)) {
    // EOFが返ってきた場合
    if (ch == EOF) {
      // ファイルの末尾かチェックする
      if (feof(in_fp)) {
        break;
      }
    }
    fputc(ch, out_fp);
  }
  
  // fclose関数でファイルを閉じる
  fclose(in_fp);
  fclose(out_fp);
}

数バイト単位で読み書きする

数バイト単位で読み書きするサンプルです。fread関数で、入力ファイルから数バイトづつ読み込み、fwrite関数で、出力ファイルへ数バイトづつ書き込みます。

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

int main(void) {
  
  // ファイルを読み込みモードで開く
  const char* in_file = "input.txt";
  FILE* in_fp = fopen(in_file, "r");
  if (in_fp == NULL) {
    fprintf(stderr, "Can't open file %s\n", in_file);
    exit(1);
  }

  // ファイルを書き込みモードで開く
  const char* out_file = "output.txt";
  FILE* out_fp = fopen(out_file, "w");
  if (out_fp == NULL) {
    fprintf(stderr, "Can't open file %s\n", out_file);
    exit(1);
  }

  // 読み込んで、他のファイルに出力
  while(1) {
    
    // 4バイトづつ読み込み
    char buffer[16];
    int32_t read_unit = 1;
    int32_t read_count = 4;
    size_t real_read_count = fread(buffer, read_unit, read_count, in_fp);
    
    // 4バイトづつ書き込み
    fwrite(buffer, read_unit, real_read_count, out_fp);
    
    if (real_read_count < read_count) {
      break;
    }
  }
  
  // fclose関数でファイルを閉じる
  fclose(in_fp);
}

入力ファイル「input.txt」です。

Hello
World!

出力ファイル「output.txt」です。

Hello
World!

ファイル全体を読み書きする

ファイル全体を読み書きするサンプルです。ftell関数fseek関数を使って、ファイルのサイズを取得して、calloc関数でメモリを確保し、fread関数で、ファイル全体を読み込み、fwrite関数で、書き込んでいます。

ファイルは、読み書きする場合に、バイナリモードで開いています。

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

int main(void) {
  // ファイルを開く
  const char* in_file = "input.txt";
  FILE* in_fp = fopen(in_file, "rb");
  if (in_fp == NULL) {
    fprintf(stderr, "Can't open file %s at %s line %d\n", in_file, __FILE__, __LINE__);
    exit(1);
  }

  // ファイルを書き込みモードで開く
  const char* out_file = "output.txt";
  FILE* out_fp = fopen(out_file, "wb");
  if (out_fp == NULL) {
    fprintf(stderr, "Can't open file %s\n", out_file);
    exit(1);
  }

  // ファイルの末尾に位置指定子を移動
  fseek(in_fp, 0, SEEK_END);
  
  // ファイルの末尾の位置を取得。これは、ファイサイズになります。
  size_t file_size = (int32_t)ftell(in_fp);
  
  // メモリ確保
  char* buffer = calloc(file_size + 1, sizeof(char));

  // ファイルの先頭に位置指定子を移動
  fseek(in_fp, 0, SEEK_SET);
  
  // ファイルを読み込み
  size_t read_count = fread(buffer, sizeof(char), file_size, in_fp);
  if (read_count != file_size) {
    fprintf(stderr, "Can't read file %s at %s line %d\n", in_file, __FILE__, __LINE__);
    exit(1);
  }
  
  // 出力ファイルへ書き込み
  fwrite(buffer, sizeof(char), file_size, out_fp);
  
  // ファイルを閉じる
  fclose(in_fp);
}

入力ファイル「input.txt」です。

Hello
World!

出力ファイル「output.txt」です。

Hello
World!

行単位でファイルから読み込むには?

C言語標準では、簡単に行単位で、ファイルから読み込む方法がありません。工夫すればできますが、自前のメモリ管理が必要になります。ネットに掲載されているものは、固定の配列を利用して、行が途中で切れる可能性があるものが多いですね。

現在(2021年6月16日記載)は、メモリ容量も大きくなったので、全部読み込んじゃって、それから処理するのが、楽ですね。

行単位で読み込みたい場合は、テキスト処理が得意なプログラミング言語を使うのもよいかもしれません。

関連情報