C言語のファイル入出力について解説

C言語のファイル入出力の解説ページアイキャッチ

このページではC言語でのファイルの入出力(読み込み・書き込み)について解説します。

ファイル入出力

まずはファイル入出力の流れについて解説していきたいと思います。

一般的に、ファイルの中身を読み込んだり、ファイルを編集したりするときは下記の流れで行うと思います。

  1. ファイルのオープン(ファイルを開く)
  2. ファイルからの読み込み or ファイルへの書き込み
  3. ファイルのクローズ(ファイルを閉じる)

C言語においても全く処理の流れは同じです。つまり、下記の3つのステップでファイルの入出力処理を行います。

  1. ファイルのオープン(ファイルを開く)
  2. ファイルからの読み込み or ファイルへの書き込み
  3. ファイルのクローズ(ファイルを閉じる)

ストリームと FILE 構造体

C言語でのファイル入出力において重要な要素が2つあります。それはストリームと FILE 構造体です。

ストリーム

ストリームとはデータの流れを表す言葉です。

Apple Music や spotify などの音楽配信サービスをストリーミングサービスと言ったりしますよね?ここで使われてるストリーム(ストリーミング)と同じ意味です。

ストリーミングサービス

ユーザーが音楽を再生したとき、このストリーミングサービスでは下記の処理が繰り返し行われることになります。

  • 再生位置の音楽データを受信してバッファ(メモリ)に格納する
  • 受信した音楽データを音として再生する
  • 次の再生位置のデータを受信したバッファ(メモリ)に上書きする
  • 受信した音楽データを音として再生する
  • ・・・・・

ここでバッファのデータに注目すると、ちょうど川の流れのように、必要なデータがどんどん流れていくことになります。聴き終わったデータはどんどんバッファから流れ出ていく感じですね。

データの流れを川の流れで表現した様子

こういったデータが連続してどんどん流れていくバッファのことをストリームといいます。音楽配信サービスもストリームを利用したサービスなのでストリーミングサービスと呼ばれたりするんです。

で、C言語のファイル入出力においても同様にストリームを利用して処理が行われます。

FILE 構造体

前述の通り、ファイルの入出力においては、まずファイルのオープンを行います。これは実際には、ファイルとプログラムの間にストリームを作成する処理になります。

ストリームのイメージ

ストリームにはファイルのデータの一部が格納され、そのストリームに対して処理を行うことで、ファイルからのデータの読み込みや、ファイルへのデータの書き込みを行うことができます。

そして、そのストリームの情報を管理するのが FILE 構造体です。ストリームのサイズやストリームバッファへのポインタなどの情報も、この FILE 構造体に格納されています。

FILE 構造体の説明図

ただし、C言語では一般的に FILE 構造体のデータを自分で作成したり変更するようなことは行わないです。標準関数を利用して FILE 構造体を扱うことになります。

例えば fopen という標準関数を使用することで自動的に FILE 構造体が作成されますし、freadfwrite などの標準関数を使用することで、その FILE 構造体に紐付けされたストリームからデータを読み込んだり書き込んだりすることができます(これらの関数の説明については後述します)。

また、先ほどストリームはデータの流れる川のようなものだと言いましたが、このデータの流れも標準関数内で実現してくれます。例えばストリーム内のデータを全て読み込んだような場合には、標準関数内で次に読み込むべきデータをストリームに移動させてきてくれます。

逆に自分で FILE 構造体を変更してストリームを制御しようと思うとめちゃめちゃ大変です。FILE 構造体のメンバのデータにアクセスすることも可能ですが、自分で変更することは避けた方が良いです。

ですので、C言語でファイル入出力を行う場合は、すでに用意されている標準関数(fopenfreadfgets などなど)を素直に使うのが良いと思います。FILE 構造体のアドレスさえ渡せば、関数内で良い感じにストリームの制御をしてくれます。

また、ここまでストリームについて解説してきましたが、実は標準関数を使用することでこのストリームを意識することなく、あたかもファイルに直接アクセスしているかのようにファイルの入出力を行うこともできます。

ただし、ストリームなのでデータがどんどん流れていく点だけは注意しましょう。例えば fread 関数などでデータを読み込んだ場合、その読み込んだデータはストリームからどんどん吐き出されていきます。この辺りは、ここから解説していく各関数の説明の中でも説明していこうと思います。

スポンサーリンク

ファイルのオープン

ファイルのオープン(ファイルを開く)に用いる代表的な関数はfopen 関数です。前述の通り、ファイルをオープンすることでストリームが作成され、そのストリームからデータをファイルを読み込んだり書き込んだりすることができるようになります。

fopen 関数

fopen 関数はファイルをオープンしてストリームを作成する関数です。

fopen

#include <stdio.h>
FILE* fopen(const char*, const char*);

fopen 関数の引数

fopen 関数の引数には下記を指定します。

  • 第一引数:開きたいファイル名(の文字列)
  • 第二引数:ファイルを開くモード(の文字列)

ファイルを開くモードの基本となるのは下記の3つです。

  • "r":読み込みモード
    • ファイルが存在しない場合はエラー
  • "w":書き込みモード
    • ファイルが存在しない場合はファイルを作成する
    • ファイルが存在する場合はファイルの先頭から上書きする
  • "a":追記モード
    • ファイルが存在しない場合はファイルを作成する
    • ファイルが存在する場合はファイルの最後から追記する

さらに各モードの後ろに + を付加することで下記のように読み込みも書き込みも可能なモードになります。

  • "r+":読み込み書き込みモード
    • ファイルが存在しない場合はエラー
  • "w+":読み込み書き込みモード
    • ファイルが存在しない場合はファイルを作成する
    • ファイルが存在する場合はファイルの先頭から上書きする
  • "a+":読み込み追記モード
    • ファイルが存在しない場合はファイルを作成する
    • ファイルが存在する場合はファイルの最後から追記する

"r+""w+" は同じようなモードになりますが、ファイルの有無により動作が異なります。"r+" は開くファイルが無い時はエラーになりますが、"w+" はエラーになりません。

また、ここまで解説してきたモードにさらに b を付加することでファイルをバイナリモードで開くことが可能です。

テキストモードとバイナリモードの大きな違いは、開いたファイルへの操作時に OS によって異なる改行コードを考慮して処理してくれるかどうかの違いです。

例えば "Hello, World\n" という文字列をファイルに書き出すとき、テキストモードでは下記のように動作が異なります。

  • Mac:'\n' をそのまま ‘\n’ で出力
  • Windows:'\n''\r\n' に置き替えて出力

つまり、テキストモードでは改行コードを OS に応じて変換して処理してくれるというわけです。

バイナリモードの場合はこのような変換は行わず、バイナリモードでは指定されたデータをそのまま出力します。

  • Mac:'\n' をそのまま '\n' で出力
  • Windows:'\n' をそのまま '\n' で出力

基本的に、

  • テキストファイル(文字列のみから作られるファイル)にはテキストモード
  • それ以外にはバイナリモード

を使用する考え方で良いと思います。

fopen 関数の戻り値

fopen 関数の戻り値は下記の通りです。

  • 成功時:ストリームの情報が格納された FILE 構造体のアドレス
  • 失敗時:NULL

第一引数で指定されたファイルのオープンに成功した場合は、そのファイル関連づけられたストリームの情報を格納した FILE 構造体のアドレスが返却されます。

fopen 関数の注意点

fopen 使用時に特に注意していただきたいのは下記の二点です。

  • 第二引数で指定するのは文字列であること
  • もともと存在するファイルを書き込みモードで開くと上書きされる

後者に関しては、せっかく苦労したファイルも書き込みモードで開いてしまうと中身が空になってしまいますので特に注意してください。

fopen 関数の使用例

例えば "input.txt" を “読み込みモード&テキストモード” で開くときは下記のように fopen 関数を実行します。

fopenの使用例1

FILE *fi;
  
/* ファイルのオープン */
fi = fopen("input.txt", "r");
if(fi == NULL){
  printf("file open error:input.txt\n");
  return -1;
}

"input.jpg を “バイナリモード” で開くときは下記のように fopen 関数を実行します。

fopenの使用例1

FILE *fi;
  
/* ファイルのオープン */
fi = fopen("input.jpg", "rb");
if(fi == NULL){
  printf("file open error:input.jpg\n");
  return -1;
}

ファイルのクローズ

fopen 関数で開いたファイルのクローズ(ファイルを閉じる)に用いる代表的な関数は fclose 関数です。

スポンサーリンク

fclose関数

fclose 関数はストリームを解放し、ファイルを閉じる関数です。

fclose

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

fclose 関数の引数

fclose 関数の引数には下記を指定します。

  • 第一引数:閉じたいファイルに関連づけられた FILE 型ポインタ

引数には fopen の戻り値である FILE 型ポインタを指定するだけであり、モードの指定なども不要ですので使い方は簡単です。

fclose 関数の戻り値

fclose 関数の戻り値は下記のようになっています。

  • 成功時:0
  • 失敗時:EOF

ですが(本当は良くないかもしれませんが…)、あまりfclose 関数の戻り値って見ないんですよね。失敗するときは指定したファイルがオープンされていない場合くらいじゃないかなぁ…。その場合はクローズが不要ですから失敗しても問題ないですよね…。

fclose 関数の使用例

下記は fclose関数の使用例になります。

fclose関数の使用例

FILE *fi;
  
/* ファイルのオープン */
fi = fopen("input.txt", "r");
if(fi == NULL){
  printf("file open error:input.txt\n");
  return -1;
}
/* ファイル入出力処理 */

fclose(fi);

ファイルの入力(ファイルからの読み込み)

ファイルの入力(ファイルからの読み込み)で使用する代表的な関数には、fgetcfgetsfread などがあります。

使用例で読み込むファイルは下記を input.txt という名前で保存したテキストファイルとして動きを解説しています。

Good Morning...
  
"Hello"

Who are you?
Good Bye!!

fgetc 関数

fgetc 関数は、ストリームから1文字読み込む関数です。1文字といっても半角英数字の1文字(unsigned char 型のデータ)と考えて良いです。テキストファイル向けの関数です。

fgetc

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

fgetc 関数の引数

fgetc 関数の引数には下記を指定します。

  • 第一引数:読み込みたいファイルの FILE 構造体のアドレス

fgetc 関数の戻り値の FILE 構造体へのポインタを指定すれば良いです。

fgetc 関数の戻り値

fgetc 関数の戻り値は下記のようになっています。

  • 成功時:読み込んだ1文字分の文字
  • 失敗時:EOF

ファイルの終端まで読み終わっている状態や読み込みに失敗した場合は EOF が返却されます。ですので、テキストファイルの場合はファイルをまだ読み込めるかどうかは fgetc 関数の戻り値が EOF であるかどうかで判断することが可能です。

MEMO

画像などのバイナリデータの場合は EOF の値自体がデータ内に存在する場合があります

ですので、戻り値が EOF の場合でもファイルの終端でない場合があり、ファイルの終端を判断することが難しいです

こういった理由でちょっとバイナリデータの読み込みには使いにくいと思います

fgetc 関数の使用例

fgetc 関数の使用例は下記の通りです。

fgetcの使用例

#include <stdio.h>

int main(void){
  FILE *fi;
  int ret;

  /* ファイルのオープン */
  fi = fopen("input.txt", "r");
  if(fi == NULL){
    printf("file open error:input.txt\n");
    return -1;
  }

  while(1){
    /* 1文字読み込み */
    ret = fgetc(fi);

    if(ret == EOF){
      /* 戻り値がEOFの場合読み込み終了 */
      break;
    }
    printf("%c", (char)ret);
  }

  /* ファイルのクローズ */
  fclose(fi);

  return 0;
}

実行すると下記のように表示されます。

Good Morning...

"Hello"

Who are you?
Good Bye!!

input.txt の中身がそのまま出力されていることが確認できると思います。

注目していただきたいのは、fi 変数は何も変更していないのに fgetc 関数で読み込むデータが次々と変化している点です。これこそがストリームの動きです。

読み込んだ文字はストリームから流れ出ていき、次の文字が次々とストリームに流れ入ってきているイメージが伝わるのではないかと思います。このようなストリームの制御は fgetc 関数の中で行われていますので、ファイルの読み込み位置の移動などは意識することなくプログラミングすることが可能です。

スポンサーリンク

fgets 関数

fgets 関数はストリームから(ファイルから)一行分データを読み込む関数です。もう少し正確にいうと、改行コード or ファイルの終端までのデータを読み込む関数になります。読み込んだ文字列の最後にはヌル文字('\0')が格納されます。

fgets 関数はテキストファイル向けの関数です。

fgets

#include <stdio.h>
char *fgets(char*, int, FILE*);

fgets 関数に関しては、下記ページで個別に解説していますので、詳しくは下記ページをご参照いただければと思います。

fread 関数

fread 関数はストリームから(ファイルから)指定したサイズ分のデータを読み込む関数です。

どちらかというとバイナリファイル向けの関数です。がテキストファイルでも使えます。ただし、fgets のように自動的に読み込んだデータにヌル文字('\0')を付加したりはしてくれませんので、テキストファイルを扱う場合はその辺りに注意しながらプログラミングする必要があります。 

fread

#include <stdio.h>
size_t fread(void*, size_t, size_t, FILE*);

fread 関数の引数

fread 関数の引数には下記を指定します。

  • 第一引数:読み込んだデータを格納するバッファ(メモリ領域)のアドレス
  • 第二引数:読み込むデータ単位のサイズ(バイト)
  • 第三引数:読み込むデータの個数
  • 第四引数:読み込みたいファイルの FILE 構造体のアドレス

“第二引数で指定したサイズ x 第三引数で指定した個数” バイトのデータが読み込まれることになります。ややこしいのは第二引数と第三引数の使い分けですね。

基本的には

  • 第二引数:第一引数のバッファ(配列等)の型のサイズ
  • 第三引数:読み込むデータ数

を指定するので良いと思います。重要なのは、読み込むデータのサイズ(つまり「第二引数 x 第三引数」)が第一引数で指定したアドレスのバッファのサイズを超えないことです。

fread 関数の戻り値

fread 関数の戻り値は下記のようになっています。

  • 読み込んだデータの個数

つまり、戻り値が「第三引数で指定したデータの個数」と同じであれば、まだ読み込めるデータが存在すると判断できますし、戻り値が「第三引数で指定したデータの個数」よりも小さいのであれば、ファイルの終端まで読み終わったと判断することが可能です。

fread 関数の使用例

fread 関数の使用例は下記の通りです。テキストファイルの読み込みを行なっています。ポイントはファイル読み込み終了の判断部分と、fgets 関数時と違って自分でヌル文字を付加している部分です。

fread関数の使用例

#include <stdio.h>
  
#define READ_SIZE 4

int main(void){
  FILE *fi;
  char str[READ_SIZE+1];
  size_t ret;

  /* ファイルのオープン */
  fi = fopen("input.txt", "r");
  if(fi == NULL){
    printf("file open error:input.txt\n");
    return -1;
  }

  while(1){
    /* READ_SIZEバイト分読み込み */
    ret = fread(str, sizeof(char), READ_SIZE, fi);

    str[ret] = '\0';
    printf("%s\n", str);
    if(ret < READ_SIZE){
      /* 戻り値が第三引数よりも小さい
         のでファイルの終端に行き着いた */
      break;
    }
  }

  /* ファイルのクローズ */
  fclose(fi);

  return 0;
}

出力結果は下記の通りです。

Good
 Mor
ning
...


"He
llo"


Wh
o ar
e yo
u?
G
ood 
Bye!
!

ファイルの出力(ファイルへの書き込み)

ファイルの出力(ファイルへの書き込み)で使用する代表的な関数には、fputcfwritefprintf などがあります。

スポンサーリンク

fputc 関数

fputc 関数はストリームへ(ファイルへ)1文字分書き込む関数です。fgetc の書き込みバージョンみたいな感じですね。

テキストファイル・バイナリファイル両方に向いている関数です。が、テキストモードだと改行コードの変換が行われることがありますので、改行コードの変換をしたくない場合は fopen 関数でバイナリモードを指定するようにしましょう。

fputc

#include <stdio.h>
int fputc(int, FILE*);

fputc 関数の引数

fputc関数の引数には下記を指定します。

  • 第一引数:書き込みたい文字
  • 第二引数:書き込みたいファイルの FILE 構造体のアドレス

第一引数は int 型ですが、実際に書き込まれる文字は int 型を unsigned char 型に変換した文字となります。

fputc 関数の戻り値

fputc 関数の戻り値は下記のようになっています。

  • 成功時:書き込んだ文字
  • 失敗時:EOF

fputc 関数の使用例

fputc 関数の使用例は下記の通りです。

fputcの使用例

#include <stdio.h>
#include <string.h> /* strlen用 */

int main(void){
  char str[] = "Hello\n World!";
  FILE *fo;
  int ret;
  int i;

  /* ファイルのオープン */
  fo = fopen("output.txt", "w");
  if(fo == NULL){
    printf("file open error:output.txt\n");
    return -1;
  }

  for(i = 0; i < strlen(str); i++){
    /* 1文字書き込み */
    ret = fputc(0x200 + str[i], fo);
  }

  /* ファイルのクローズ */
  fclose(fo);

  return 0;
}

実行すると output.txt が作成されます。ファイルの中身は下記のようになります。

Hello
 World!

読み込みで用いた fgetc 関数同様に、fputc 関数実行するたびにファイルの書き込み位置が変化している点がポイントですね。これも fputc 関数の中でデータが流れていくようにストリームの処理を行なってくれているからです。

fprintf 関数

fprintf 関数は、おなじみの printf 関数と同じような使い方でストリーム(ファイル)に書き込みを行う関数です。テキストファイル向けの関数です。

fprintf

#include <stdio.h>
int fprintf(FILE*, char*, ・・・);

fprintf 関数の引数

fprintf関数の引数には下記を指定します。

  • 第一引数:書き込みたいファイルの FILE 構造体のアドレス
  • 第二引数:書式文字列
  • 第三引数:引数リスト

第一引数以外は printf 関数と全く同じ引数となります。

実は第一引数に stdout を指定すると、標準出力に文字が出力され、printf と同じ働きをします

fprintf 関数の戻り値

fprintf 関数の戻り値は下記の通りです。

  • 成功:書き込んだ文字数
  • 失敗:負の値

fprintf 関数の使用例

fprintf 関数の使用例は下記の通りです。

fprintf関数の使用例

#include <stdio.h>
  
int main(void){
  FILE *fo;
  char *ret;
  char name[5][10] = {
    "taro", "hanako", "miki", "ziro", "emi"
  };
  int i;

  /* ファイルのオープン */
  fo = fopen("output.txt", "w");
  if(fo == NULL){
    printf("file open error:output.txt\n");
    return -1;
  }

  for(i = 0; i < 5; i++){
    fprintf(fo, "%d : %s\n", i+1, name[i]);
  }

  /* ファイルのクローズ */
  fclose(fo);

  return 0;
}

実行して作成される output.txtの中身は下記のようになります。

1 : taro
2 : hanako
3 : miki
4 : ziro
5 : emi

fwrite 関数

fwrite 関数はストリームへ(ファイルへ)指定したサイズ分データを書き込む関数です。fread 関数の書き込みバージョンみたいな感じです。fwrite 関数はどちらかというとバイナリファイル向きの関数です。テキストファイルでも使用できますが、改行コードの変換に注意しましょう。

fwrite

#include <stdio.h>
size_t fwrite(void*, size_t, size_t, FILE*);

fwrite 関数の引数

fwrite 関数の引数には下記を指定します。

  • 第一引数:書き込むデータを格納するバッファ(メモリ領域)のアドレス
  • 第二引数:書き込むデータ単位のサイズ(バイト)
  • 第三引数:書き込むデータの個数
  • 第四引数:書き込みたいファイルの FILE 構造体のアドレス

基本的に fread 関数と同様にして引数を指定すれば良いです。読み込むのか、書き込むのかが違うくらいですね。

fwrite 関数の戻り値

fwrite 関数の戻り値は下記の通りです。

  • 書き込んだデータの個数

fwrite 関数の使用例

fwrite 関数の使用例は下記の通りです。input.txt を読み込み、そのままコピーして output.txt に書き出す使用例としています。

fwrite関数の使用例

#include <stdio.h>

#define READ_SIZE 10

int main(void){
  FILE *fi;
  FILE *fo;
  
  char str[READ_SIZE];
  size_t ret;
  
  /* ファイルのオープン */
  fi = fopen("input.txt", "r");
  if(fi == NULL){
    printf("file open error:input.txt\n");
    return -1;
  } 
  
  fo = fopen("output.txt", "w");
  if(fo == NULL){
    printf("file open error:output.txt\n");
    fclose(fi);
    return -1;
  } 
  
  while(1){
    /* READ_SIZEバイト分読み込み */
    ret = fread(str, sizeof(char), READ_SIZE, fi);
    
    fwrite(str, sizeof(char), ret, fo);
    
    if(ret < READ_SIZE){
      /* 戻り値が第三引数よりも小さい
         のでファイルの終端に行き着いた */
      break; 
    } 
  } 
  
  /* ファイルのクローズ */
  fclose(fi);
  fclose(fo);
  
  return 0;
}

実行して作成される output.txt の中身は下記のようになります。

Good Morning...

"Hello"

Who are you?
Good Bye!!

スポンサーリンク

まとめ

このページではファイルの入出力の仕組みやファイル入出力に使用する標準関数の仕方について解説しました。ファイル入出力が出来るとC言語でできることの幅がかなり広がりますのでしっかりマスターしておきましょう!

オススメの参考書

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示