こんにちは、ナナです。
パソコンの中には、様々な情報が「ファイル」として保存されています。
皆さんはパソコンを利用するときに、音楽ファイルを開いて音楽を聴いたり、テキストファイルを開いてメモを書き込んだりして管理されていることでしょう。
このように、コンピュータの中では「ファイル」という枠組みで情報のカタマリを管理しているのです。
プログラムというのは、コンピュータに対する指示を皆さんの代わりに行ってくれるものです。つまり、皆さんはプログラムを通して「ファイル」への情報を読み書きすることもできるのです。
本記事では次の疑問点を解消する内容となっています。
それでは、ファイル制御の基本となる「ファイル」の開き方と閉じ方を学びましょう。
ファイルの開き方:fopen関数の仕様と使い方
師匠!プログラムから「ファイル」を操ることができる、と聞きました。
それは、わたしが覚えた忍術の名を、メモ帳アプリに打ち込むことと同じことが、プログラムからもできるってことなんですか?!
その通り!プログラムから「ファイル」を読み書きすることもできるんだよ。
「ファイル」をプログラムから操るためには、まず「ファイル」を開く必要があるんだ。じゃあ、最初に「ファイル」の開き方を学ぼうね。
プログラムから「ファイル」を操ることで、大量のデータをプログラムからファイルに書き込んだり、データを読み出したりすることができます。
ファイルを制御するための第1歩として、ファイルの開き方を学びましょう!
fopen:ファイルを開くための標準ライブラリ関数
「ファイル」を制御するために、必ず必要となる関数が「fopen関数」です。
標準ライブラリ関数として提供されるため、ヘッダファイルをインクルードすれば皆さんも使うことができます。
#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);
errno_t fopen_s(FILE ** fp, const char * filename, const char * mode);
頭文字の「f」は「file(ファイル)」を示しており、「file open」を略して「fopen」です。
「fopen_s関数」はより安全性を高めたセキュア版の関数です。使い方は後半で解説しましょう!
fopen関数の仕様
fopen関数は「ファイル」に対する制御を可能とする「ファイルハンドル(ファイルディスクリプタ)」を戻り値で返却する関数です。
includeファイル | stdio.h |
関数仕様 | FILE * fopen(const char * filename, const char * mode); |
引数1 | 開きたいファイルの名前 |
引数2 | 開く時のモード |
戻り値 | ファイルハンドル(ファイルディスクリプタ) |
特記事項 | ファイルが開けない時は、戻り値がNULLポインタとして返却される。 |
第2引数の文字列としてモードを指定します。いくつかの種類があり、目的に応じてモードの種類を切り替える必要があります。
モード | 操作 | 説明 |
---|---|---|
“r” | 読み取り | 指定ファイルが存在しない場合は、NULLポインタが返却される。 |
“w” | 書き込み | 指定ファイルが存在する場合は、内容を破棄して開く。 |
“a” | 追記 | 指定ファイルが存在する場合は、内容を維持し追記として開く。 指定ファイルが存在しない場合は、新規ファイルとして開く。 |
“r+” | 読み書き | 指定ファイルが存在しない場合は、NULLポインタが返却される。 |
“w+” | 読み書き | 指定ファイルが存在する場合は、内容を破棄して開く。 |
“a+” | 読み取り/追記 | 指定ファイルが存在する場合は、内容を維持し追記として開く。 指定ファイルが存在しない場合は、新規ファイルとして開く。 |
ファイルを「fopen」する際には、ファイル名を指定しますが、指定したファイルがすでに存在しているのか、していないのかによって各モードの動作が異なることに注意が必要です。
例えば、存在しないファイル名に対して「r」による読み取りを指定しても、ファイルが存在しないのですから、NULLポインタで異常終了するというわけです。
また、次のようにモードに対して「b」を追加すると「バイナリファイル」として制御することになります。
モード | 動作内容 |
---|---|
“rb” | 読み込み可能(バイナリファイル) |
“wb” | 書き込み可能(バイナリファイル) |
“ab” | 追記で書き込み可能(バイナリファイル) |
「b」を指定しない場合は「テキストファイル」を制御することになります。
「r(read:読み出し)」「w(write:書き込み)」「a(append:追記)」という意味になります。
fopen関数を使ったサンプルプログラム
それでは実際に「fopen関数」を使ったサンプルプログラムを紹介しましょう。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
// memo.txtファイルを書き込み可能でオープン
fp = fopen("memo.txt", "w");
return 0;
}
このプログラムを動かすと、プロジェクトフォルダの中に「memo.txt」が作られます。
書き込みモードでは、同名のファイルがすでに存在していても新規ファイルとしてファイルを再作成します。
まだ、ファイルを新しく作成しただけなので、ファイルを開いても中身は空っぽですよ。
このプログラムはまだ不完全なものですが、「fopen関数」の呼び出し方として紹介しています。何が不完全なのかは、続きを読んでみてください!
「_CRT_SECURE_NO_WARNINGS」のマクロ定義について
fopen関数を呼び出すためには「stdio.h」をインクルードする前に、「_CRT_SECURE_NO_WARNINGS」のマクロ定義を行う必要があります。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
これを定義しないと、次のビルドエラーが発生します。
error C4996: ‘fopen’: This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
fopen関数は安全ではないため、fopen_s関数を使うように促しています。ただし、「
_CRT_SECURE_NO_WARNINGS」を定義することで使用することができます。
「fopen_s関数」の使い方は後述します。気にせず、続きを読んでいきましょう!
ファイルオープンで取得する「ファイルハンドル」とは
師匠!ファイルを作ることができました。いつもメモ帳アプリで「名前を付けて保存」をして作っていたのに、プログラムから作ることができるんですね~。
でも、「fopen関数」の戻り値の「fp(ファイルハンドル)」っていったい何なんですか?プログラムの中で、何も使ってないですよね?
FILE構造体のポインタ変数である「fp」ですね。ファイルを扱うときは必ず出てくる「ファイルハンドル」と呼ばれるものです。「ファイルポインタ」や「ファイルディスクリプタ」なんて呼び方もされますよ。
それでは「ファイルハンドル」というものが、何なのかを理解していきましょう!
fopen関数の戻り値は「ファイルハンドル」と呼ばれるものです。ファイル制御を行うときに必要となるのが、この「ファイルハンドル」なのです。
ファイルハンドルの役割とは
「ハンドル」が付いている代表的な身の回りのものといえば車です。「ハンドル」を操作することで「車」を操ることができます。
つまり、「ファイルハンドル」とは
「ファイル」を操るための「ハンドル」
なのです。
皆さんがファイルを操るためには、「ファイルハンドル」を作り出すことで初めて可能となります。
「ファイルハンドル」は1つのファイルに対して1つ紐づく
fopen関数の第1引数には、ファイル名を指定する必要があります。つまり、1つのファイルに対してファイルハンドルを1つ作り出すことになります。
それではもし、複数のファイルを同時に扱いたい時がケースがあった場合はどうすればよいでしょうか?
そうです。ファイルハンドルを2つ作り出せばよいのです。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE * fp1 = NULL; // ファイルハンドル1
FILE * fp2 = NULL; // ファイルハンドル2
// 2つのファイルを書き込み可能でオープン
fp1 = fopen("memo1.txt", "w");
fp2 = fopen("memo2.txt", "w");
return 0;
}
皆さんはパソコンにおいて、例えばExcelファイルを2つ同時に開くことがありますよね。ファイルハンドルを同時に2つ扱うということは、これと同じことなのです。
「1つのファイルに対して、1つのファイルハンドルが紐づく」
これは非常に大事なことなんです。ハンドルという概念を理解する上で、必ず理解しておく必要があります。
FILE構造体とは
ファイルハンドルを管理するための変数には、FILE構造体をポインタ変数で用意します。
FILE * fp = NULL;
fopenの戻り値となる「ファイルハンドル」は、このFILEポインタ型の変数に代入することになります。
fp = fopen("memo.txt", "w");
「FILE構造体」は、ファイルを管理するための情報が詰め込まれたデータとなっています。
fopen関数を呼び出すことで、FILE構造体のメモリが確保され、そのメモリ番地が戻り値で返却されます。このようなハンドルのメモリ領域は、一般的に「ヒープメモリ」上に確保されます。
そのため、ハンドルのメモリ領域を解放するためには、明示的なメモリ解放手続きが必要となります。
この解放手続きは、後述する「fclose関数」が担っています。
「ヒープメモリ」に関して忘れてしまった方は『C言語 動的メモリ【ヒープメモリの使い方と獲得する方法】』を見ておきましょう!
「ファイル」の閉じ方:fclose関数の使い方
師匠!「ファイルハンドル」を作り出したというのは、わたしがプログラムでファイルを開いている状態を示しているのですね。
「ファイルハンドル」を作りまくれば、何個もファイルを作り出すことができるってことですね。でもそんなことしたら、ファイルハンドルが溢れかえってしまいますよ!
そうだね。皆はパソコンでファイルを同時に複数開くこともあるよね。でも、使い終わったらファイルを閉じますよね。
「ファイルハンドル」も使い終わったら最後に閉じてあげる必要があります。ファイルを閉じる方法を教えましょう。
fclose:ファイルを閉じるための標準ライブラリ関数
fopen関数で開いた「ファイル」を閉じるときに必要となる関数が「fclose関数」です。
#include <stdio.h>
int fclose(FILE * fp);
ファイルを開く時には「fopen_s関数」がありましたが、ファイルを閉じるときは「fclose関数」しかありません。
fclose関数の仕様
fclose関数は「ファイルハンドル」を閉じるための関数です。引数には閉じたい「ファイルハンドル」を指定します。
includeファイル | stdio.h |
関数仕様 | int fclose(FILE * fp); |
引数1 | 閉じるべきファイルハンドルを指定する。 |
戻り値 | 正常終了時は0を返却。異常時はEOFを返却。 |
特記事項 | 引数には必ずfopen関数を呼び出したときのファイルハンドルを渡すこと。 |
fclose関数を使ったサンプルプログラム
fclose関数は次のように使用します。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
fp = fopen("memo.txt", "w");
// ファイルをクローズ
fclose(fp);
return 0;
}
fopen関数で取得したファイルハンドルを、fclose関数の引数に設定するだけですね。非常に簡単な使い方です。
「fopen関数」を使ってオープンしたファイルハンドルは、使い終わったら必ず「fclose関数」でクローズしましょう!
fopen_s関数を使ったファイルハンドルの作り方
「fopen_s関数」は、「fopen関数」のセキュア版(安全性を高めた関数)となっています。
概要は同じですが、引数や戻り値の構成が少しだけ変化しています。使い方を覚えておきましょう!
fopen_s関数の仕様
includeファイル | stdio.h |
関数仕様 | errno_t fopen_s(FILE ** fp, const char * filename, const char * mode); |
引数1 | ファイルハンドルへのポインタ |
引数2 | 開きたいファイルの名前 |
引数3 | 開く時のモード |
戻り値 | エラーコード。正常終了時は0が返却。 |
特記事項 | エラーコードはマイクロソフト社の『エラーコード一覧』を参照のこと |
第1引数がファイル構造体の「ダブルポインタ」になっています。
ダブルポインタに関して忘れてしまった人は『C言語 「ポインタのポインタ」を図解【イメージで簡単理解!】』を参照して下さい。
この関数は「ダブルポインタ」が必要となる代表的なシーンに該当していますよ。
fopen_s関数を使ったサンプルプログラム
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
errno_t no;
no = fopen_s(&fp, "memo.txt", "w");
if (no != 0)
{
printf("ERR(%d):ファイルのオープンに失敗しました", no);
}
fclose(fp);
return 0;
}
fopen_s関数の第1引数の使い方に注意しましょう。
ファイル型のポインタ変数を定義し、アドレス演算子「&」を付けて引数となるダブルポインタ変数へ受け渡しています。
fopen関数とfopen_s関数の動作の違いは?
セキュア版である「fopen_s関数」では、書き込みモードで同一ファイルを同時にオープンすることを禁止しています。そのため次のプログラムは、2回目の呼び出しで異常終了します。
#include <stdio.h>
int main(void)
{
FILE * fp1 = NULL;
FILE * fp2 = NULL;
errno_t no1;
errno_t no2;
// 1回目のファイルオープン
no1 = fopen_s(&fp1, "memo.txt", "w");
if (no1 != 0)
{
printf("ERR(%d): open 1回目", no1);
}
// 2回目のファイルオープン
no2 = fopen_s(&fp2, "memo.txt", "w");
if (no2 != 0)
{
printf("ERR(%d): open 2回目", no2);
}
return 0;
}
ERR(13): open 2回目
このように2回目のfopen_s関数の呼び出しは失敗しています。エラーコードは13番ですね。
エラーコードはマイクロソフト社のHPを見るとわかります。『エラーコード一覧』を見てみると13番は「EACCES アクセス許可は拒否されました」と定義されています。
同一のファイルを書き込み指定で2つ開くと、編集作業がバッティングしますよね。だから、禁止するのです。
fopen_s関数を使うと、このような危険な処理を禁止できるのです。fopen関数では開けてしまいますよ。
Q&A:ファイルの開き方・閉じ方に関するよくある質問
ファイルに関して質問したい人はいるかな?
Q:もし、ファイルハンドルをクローズしなかったら、ファイルはどうなっちゃうの?
し、師匠!大事件です・・・。
わたくし「ファイルハンドル」をクローズせずに、プログラムを終わらせてしまいました。ファイルはいったいどうなっちゃうの?
Windows上で動かしているアプリケーションなら、プログラムが終わるとファイルはクローズされちゃうから問題ないね。
でもね、プログラムとしてはやっぱり良くないんだよ。「開いたら、閉じる」それはお作法なんだよ。
本記事の最初にお見せしたプログラムは、ファイルをオープンはしたけどクローズをしていませんね。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
fp = fopen("memo.txt", "w");
// ファイルハンドルをクローズするの忘れちゃった・・・
return 0;
}
ファイルハンドルはヒープメモリ上に確保されており、プログラムが終わると共にヒープメモリは解放されます。
ただし、このようなプログラムを書いているようでは「私はプログラム初心者です」と周りに言っているとの変わりません。プログラミングを職業として目指している方は、決してやってはなりませんよ。
組み込み開発はプログラムが終わらない性質をもっているため、このようなメモリリークは決して許されません。
だからこそ、「オープンしたら閉じる」を肝に銘じましょう!
Q:「バイナリファイル」って何?
師匠!fopen関数でモードに「b」を付けると「バイナリファイル」で開くって教えてもらいました。
「バイナリファイル」って何ですか?
コンピュータで扱うファイルは大別すると、「テキストファイル」と「バイナリファイル」の2つに分類されます。
「テキストファイル」は文字としての情報を管理しているファイルです。「バイナリファイル」は
「テキストファイル」はASCIIコードなどの「文字コード」の情報で構成されたファイルのため人が読むことができます。
それに対し、「バイナリファイル」は文字コード以外の情報も含んだファイルのため、文字として読めない情報が管理されているのです。
バイナリファイルの扱いに関しては、スピンオフの記事『C言語 fread/fwrite【バイナリファイルの書き込み・読み込み】』にまとめてあるので、興味のある方は読むとよいでしょう。
メモ帳アプリは「テキストファイル」を開くための「テキストエディタ」と呼ばれる種類のエディタです。
そのため「バイナリファイル」を開くと文字化けして読めない部分が出てくるのです。
課題:ファイルの開き方・閉じ方を学べたかを確認しよう
課題1
課題内容
fopen関数を使って、次のモードでファイルを開きなさい。
オープン関数 | モード | ファイル名 |
---|---|---|
fopen | 書き込み | Hello.txt |
プロジェクトフォルダ内に「Hello.txt」が出来上がっていることを確認せよ。
出力期待結果
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
fp = fopen("Hello.txt", "w");
fclose(fp);
return 0;
}
書き込みモードである「w」を指定した場合は、指定ファイルが存在しても、していなくても中身が空っぽのファイルが生成されます。
ファイルハンドルは最後にfclose関数でクローズ処理を行いましょう!
課題2
課題内容
fopen関数を使って、次のモードでファイルを開きなさい。
オープン関数 | モード | ファイル名 |
---|---|---|
fopen | 読み取り | Hello.txt |
ファイルハンドルが正常に取得できた場合は「ファイルがオープンできました」、取得できなかった場合は「ファイルのオープンに失敗しました」と表示せよ。
プロジェクトフォルダに事前に「Hello.txt」が存在している場合と、存在していない場合の2通りで確認せよ。
出力期待結果
「Hello.txt」ファイルが存在する場合
ファイルがオープンできました
「Hello.txt」ファイルが存在しない場合
ファイルのオープンに失敗しました
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
fp = fopen("Hello.txt", "r");
if (fp == NULL)
{
printf("ファイルのオープンに失敗しました");
}
else
{
printf("ファイルがオープンできました");
fclose(fp);
}
return 0;
}
fclose関数はファイルオープンが成功したときにしか利用できません。注意しましょう。
モードが読み取り「r」で指定ファイルが存在しない場合は、fopen関数の呼び出しは失敗してNULLポインタが返却されます。
課題3
課題内容
fopen_s関数を使って、次のモードでファイルを開きなさい。
オープン関数 | モード | ファイル名 |
---|---|---|
fopen_s | 追記 | Hello.txt |
※「fopen」ではなく、「fopen_s」関数を使用せよ。
出力期待結果
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
fopen_s(&fp, "Hello.txt", "a");
fclose(fp);
return 0;
}
fopen関数とfopen_s関数は引数構成が異なります。第1引数の扱い方に注意しましょう。
追記モードは「a」を指定するのでしたね。実際の追記方法は次の記事で学びますよ!