このページでは、C言語の fgets
関数について解説していきます!
fgets
関数はテキストファイルの読み込みを行う関数です。プログラムからテキストファイルを読み込みたくなることは結構多いので、いろんな場面で活躍してくれる関数だと思います。是非使い方を覚えていってください!
Contents
fgets
関数
fgets
関数はテキストファイルから1行分の文字列を取得する関数になります。
fgets
関数のプロトタイプ宣言は下記のようになります。
#include <stdio.h>
char *
fgets(char * restrict str, int size, FILE * restrict stream);
各引数の意味は下記のようになります(後述で各引数の意味も含めて詳細を解説していきます)。
str
:取得した文字列の格納先となるバッファの先頭アドレスsize
:一度に取得する最大文字数+1
stream
:読み込み先のストリーム
ストリームと聞くと難しく感じるかもしれませんが、次に紹介する fgets 関数の使用例 の通り、fopen
でファイルを開く場合は、fopen
の返却値であると考えて良いです(fgets
関数を使用する場合 fopen
時には読み込みモードを指定しておく必要があります)。
ストリームに関しては下記ページでも解説していますので、詳しく知りたい方は下記ページもご参照いただければと思います。
C言語のファイル入出力について解説fgets
関数の使用例
fgets
関数のイメージが湧きやすいよう、まずは fgets
関数を利用したサンプルを紹介したいと思います。
そのサンプルは下記となります。
#include <stdio.h>
#define MAX_STR_LEN 10
int main(void) {
char str[MAX_STR_LEN];
FILE *stream = fopen("text.txt", "r");
if (stream == NULL) {
printf("fopen error\n");
return 0;
}
while (fgets(str, MAX_STR_LEN, stream) != NULL) {
printf("[%s]\n", str);
}
fclose(stream);
return 0;
}
このソースコードのプログラムでは、text.txt
というファイルを fgets
関数によって読み込んで文字列を取得し、その取得した文字列を [
]
で括って表示を行う処理を行なっています。
ファイルの最後まで文字列の取得と表示を行い、ファイルの最後まで表示が完了したら、プログラムを終了するようになっています。
例えば text.txt
の中身が下記のようなものであれば、
1234 abcdefghijklmnopq rstuvwxyz
実行結果としては下記のように表示が行われることになります。[
]
で囲んだ部分が、一度の fgets
関数の実行によって取得できた文字列となります。
[1234 ] [abcdefghi] [jklmnopq ] [rstuvwxyz]
ポイントは、text.txt
の2行目が2回の fgets
関数の実行に分けて取得されているところだと思います。この辺りの理由も含めて、fgets
関数の詳細な解説を行なっていきたいと思います。
スポンサーリンク
fgets
関数の動作
次は、fgets
関数の動作について、fgets 関数の使用例 で紹介したソースコードを参照しながら解説していきたいと思います。
fgets
関数によって取得できる文字列
fgets
関数は、引数 stream
のストリームから文字列を取得する関数です。引数 stream
に同じものを指定して再度実行した場合には、前回読み込んだ次の文字から読み込みが開始されることになります。
引数 size
が十分大きいものであると考えると、fgets
関数は1行分の文字列を読み込む関数であると考えることができます。
もう少し具体的にいうと、fgets
関数は「読み込みを開始した最初の文字から行の終わり」までを読み込み、読み込んだ文字を str
を先頭アドレスとするバッファ(配列など)に格納します。読み込みを開始する位置が行の始まりであれば、1行分読み込むことができます。
そして、読み込んだ文字の後ろ側にはヌル文字('\0'
)が追加で格納されます。もし改行されている行であれば、改行文字も含んでバッファに格納されます。
ただし、現実的には fgets
関数によって読み込むことができる “最大の文字数” は size - 1
となります。
1文字が1バイトであるとは限らないため、”最大の文字数” ではなく “最大のバイト数” が size - 1
であるといった方が正しいですが、このページでは1文字1バイトであることを前提とし、以降も文字数という言葉を使って解説していきます
ですので、size - 1
文字読み込んでも行の終わりに到達しない場合、その最初の文字から size - 1
文字のみが読み込まれ、バッファに格納されることになります(この場合も最後にはヌル文字('\0'
)が格納される)。
そのため、テキストファイルの行の長さや引数 size
の大きさによっては、必ずしも1行分の文字列を読み込むことができるわけではないという点に注意してください。
例えば、fgets 関数の使用例 で紹介したソースコードにおいては引数 size
を MAX_STR_LEN
、すなわち 10
としていますので、fgets
関数で読み込まれる最大の文字数は 9
となります。
実行例を示す際に使用した text.txt
の内容は下記でしたので、
1234 abcdefghijklmnopq rstuvwxyz
2行目に関しては 9
文字を超えるため1度に1行分を取得することができず、下記のように2回に分けて fgets
関数から取得されて表示されていました(改行文字も含めて取得されるので、最後に改行が入っています)。
[abcdefghi] [jklmnopq ]
それに対して1行目と3行目の文字数は 9
以下ですので、fgets
関数で一度に1行分の文字列を取得することができています。
[1234 ]
[rstuvwxyz]
もちろん引数 size
に指定する値を大きくすれば、2行目も一度に1行分取得することができるようになります。ただ、引数 size
に応じてバッファのサイズも大きくする必要がある点に注意してください。これに関しては、次の fgets 関数に必要なバッファのサイズ で説明します。
また、text.txt
の最後の行に関しては改行が行われていませんので、上記のように最後の行の文字列取得結果には改行文字が含まれないことになります。
fgets
関数に必要なバッファのサイズ
前述の通り fgets
関数では一度に最大 size - 1
文字が読み込まれ、その読み込んだ文字+ヌル文字、すなわち最大 size
文字が str
を先頭アドレスとするバッファに格納される可能性があります。
したがって、 str
を先頭アドレスとするバッファのサイズは、最低でも size
文字を格納できるサイズである必要があります。もし、バッファが size
文字を格納できないようなサイズである場合、fgets
関数の中でそのバッファを超えて文字を格納することになり、バッファオーバーランが起こってプログラムに脆弱性が生まれる可能性もあります。
このようなバッファオーバーランが起こらないよう、fgets
関数の第2引数 size
には、str
を先頭アドレスとするバッファのサイズ(例えば char
型の配列の要素数)を指定することが多いです。
fgets 関数の使用例 におけるバッファは配列 str
になります。配列 str
の要素数は MAX_STR_LEN
なので、最大 MAX_STR_LEN
個の文字を格納することが可能です。
さらに fgets
関数を実行する際に、第1引数に配列 str
の先頭アドレスを指定しているため、読み込まれた文字は配列 str
に格納されていくことになります。この時、第2引数には配列 str
の要素数である MAX_STR_LEN
を指定しているため、fgets
関数で一度に読み込まれる文字数は最大 MAX_STR_LEN - 1
となり、読み込んだ文字の後ろにヌル文字を追加したとしても全ての文字を配列 str
内に収めることができます。
スポンサーリンク
fgets
関数の返却値
続いて、fgets
関数の返却値について説明します。
fgets
関数は正常に文字列の取得ができた場合、第1引数で指定した str
と同じアドレスを返却します。
それに対し、すでにファイルの最後まで読み込みが終わっている stream
に対して fgets
関数が実行された場合や、fgets
関数内でエラーが発生した場合は NULL
を返却します。
したがって、まだ stream
から fgets
関数で読み込みできるかどうかは、fgets
関数の返却値が NULL
であるかどうかで判断することができます。
fgets 関数の使用例 で示したソースコードの下記部分では、上記を利用してファイルの最後まで文字列を取得することを実現しています。
while (fgets(str, MAX_STR_LEN, stream) != NULL) {
printf("[%s]\n", str);
}
ただし、前述の通り、fgets
関数は内部でエラーが発生した場合にも NULL
を返却するため、fgets
関数の返却値だけでは、エラーが発生したのか、ファイルの最後まですでに読み込みが完了したのかの判別ができません。
もし、この2つを判別したいのであれば、下記で示す feof
関数や ferror
関数を利用してください。
#include <stdio.h>
int feof(FILE *stream);
#include <stdio.h>
int ferror(FILE *stream);
両者とも引数 stream
の状態をチェックする関数で、feof
関数は stream
が終端まですでに到達しているかどうかをチェックし、すでに到達している場合は 0
以外の値を返却します。
ですので、fgets
関数が NULL
を返却した後に、fgets
関数の引数に指定した stream
と同じものを feof
関数の引数に指定して実行することで、fgets
関数が NULL
を返却した理由を知ることができます。具体的には、feof
関数が 0
以外の値を返却した場合、fgets
関数が NULL
を返却した理由が「全ての文字列を正常に読み込んだから」であると判断することができます(返却値が 0
の場合は「エラーが発生したから」と判断)。
また ferror
関数は、stream
に対する処理でエラーが発生したかどうかをチェックし、エラーが発生している場合は 0
以外を返却します。
ですので、fgets
関数が NULL
を返却した後に、fgets
関数の引数に指定した stream
と同じものを ferror
関数の引数に指定して実行することで、fgets
関数が NULL
を返却した理由を知ることができます。具体的には、ferror
関数が 0
以外の値を返却した場合、fgets
関数が NULL
を返却した理由が「エラーが発生したから」であると判断することができます(返却値が 0
の場合は「全ての文字列を正常に読み込んだから」と判断)。
例えば、fgets 関数の使用例 で示したソースコードにおいて、正常にファイルの読み込みが終了した場合は read success!!!
を、エラー終了した場合は read failure...
を表示するようにしたい場合、feof
関数を利用すれば下記のように記述することができます。
#include <stdio.h>
#define MAX_STR_LEN 10
int main(void) {
char str[MAX_STR_LEN];
FILE *stream = fopen("text.txt", "r");
if (stream == NULL) {
printf("fopen error\n");
return 0;
}
while (fgets(str, MAX_STR_LEN, stream) != NULL) {
printf("[%s]\n", str);
}
if (feof(stream) != 0) {
printf("read success!!!\n");
} else {
printf("read failure...\n");
}
fclose(stream);
return 0;
}
また、ferror
関数を利用した場合は下記のように記述することができます。
#include <stdio.h>
#define MAX_STR_LEN 10
int main(void) {
char str[MAX_STR_LEN];
FILE *stream = fopen("text.txt", "r");
if (stream == NULL) {
printf("fopen error\n");
return 0;
}
while (fgets(str, MAX_STR_LEN, stream) != NULL) {
printf("[%s]\n", str);
}
if (ferror(stream) == 0) {
printf("read success!!!\n");
} else {
printf("read failure...\n");
}
fclose(stream);
return 0;
}
ちなみに、fgets
関数は、引数 stream
に書き込みモードで開いたストリームを指定することで、簡単にエラーを発生させることができます。
例えば下記のように fopen
実行部分を変更すれば、最初に fgets
関数が実行された際にエラーを発生させることができます。ただし、モード "w"
で fopen
しているため、実際に実行すると text.txt
の中身が空になってしまうので気をつけてください。試す場合は text.txt
が空になっても問題ない状態にしてから試してください(text.txt
を退避しておくなど)。
FILE *stream = fopen("text.txt", "w");
fgets
関数利用時の注意点
最後に fgets
関数利用時の注意点について記載しておきます。
fgets
関数を利用する場合は読み取りモードで開く
先ほど少し触れましたが、fgets
関数で読み込みを行う場合、当然読み込みモードを指定してファイルを開く(ストリームを開く)必要があるので注意してください。
例えば、下記のように書き込みモードを指定して取得したストリームに対して fgets
関数を実行するとエラーになります。
FILE *stream = fopen("text.txt", "w");
スポンサーリンク
改行文字が含まれる場合と含まれない場合がある
また、fgets
関数で取得した文字列には改行文字が含まれる場合と含まれない場合があります。例えばファイルの最後の行に改行がない場合、その行の取得結果には改行文字は含まれません。
そのため、全ての行に改行文字が含まれている or 全ての行に改行文字が含まれていないことを前提にして処理を行うようにすると、うまくプログラムが動作しない可能性があるので注意してください。
もし、全ての行を改行なしで統一したい場合は、例えば下記のように改行文字をヌル文字で上書きするような対策が考えられます。
#include <stdio.h>
#include <string.h>
#define MAX_STR_LEN 10
int main(void) {
char str[MAX_STR_LEN];
FILE *stream = fopen("text.txt", "r");
if (stream == NULL) {
printf("fopen error\n");
return 0;
}
while (fgets(str, MAX_STR_LEN, stream) != NULL) {
size_t len = strlen(str);
if (len > 0) {
if (str[len - 1] == '\n') {
str[len - 1] = '\0';
}
}
printf("[%s]\n", str);
}
fclose(stream);
return 0;
}
まとめ
このページでは、C言語の標準ライブラリ関数である fgets
関数について解説しました!
fgets
関数を利用することで、テキストファイルから1行分の文字列を読み込んで取得することができます。ただし、fgets
関数で読み込める最大文字数は size - 1
のみであり(size
は fgets
関数の第2引数として指定)、1行の文字数が size - 1
を超えた場合、一度の fgets
関数の実行だけでは1行分の文字列を取得することができないので注意してください。
size
を大きくすれば、一度に読み込める文字数も増えますが、size
に応じてあらかじめ用意しておくバッファのサイズも大きくする必要がありますので、この点にも注意が必要です。
C言語といえども、結構テキストファイルを扱いたくなるような場面も多いと思います。そんな時に fgets
関数が活躍することも多いですので、是非使い方を覚えておいてください!