何かと悪名高い gets 関数ですが、確かにバッファオーバーランを防げないなど、安全性に欠けるところがあります。そこで、これを何とかするために、gets を記述していた箇所を単純に fgets に置き換えればそれで問題が解決したと考えている方も少なくないようです。
ところが、実際にはそんな簡単なものではありません。gets と fgets では仕様が異なりますし、gets のときは考えなくてもよかったことが、fgets では発生します。まずは具体例から見ていきましょう。
char s[5]; gets(s);
上のようなコードを書いた場合、5 文字以上を入力しようとするとたちまちバッファオーバーランが発生してしまいます。そこで、
char s[5]; fgets(s, 5, stdin);
とすることで解決したと安心してしまうわけですが、この場合に 5 文字以上を入力した場合にどうなるかを考えてみてください。例えば、
12345
を入力したとすると、配列 s には "1234" が格納されます。s[4] には '\0' が格納されます。そして、ストリームには格納し切れなかった'5' と改行文字が残ります。当然、この後次の行を入力しようとすると、"5\n" が読み込まれることになるわけです。これは gets では考慮する必要がなかったことです。
また、4 文字ちょうどを入力した場合、例えば、
1234
を入力したとすると、やはり配列 s には "1234" が格納され、ストリームに改行文字が残ります。当然、次に getchar で 1 文字読み込もうとすると改行文字が出てきますし、fgets で 1 行読み込もうとすると、改行文字だけが読み込まれることになります。gets ではこのようなことは起こりませんでした。
さらに、3 文字以下を入力した場合、例えば、
123
を入力したとすると、配列 s には "123\n" が格納されます。gets のときには "123" が格納されたので、最後の改行文字が余分に格納されていることになります。入力した文字列が例えばファイル名だった場合、そのまま fopen 関数に渡そうとすると、(改行文字が含まれているので)当然失敗します。
多くの掲示板やメーリングリスト、ウェブサイト、ときには書籍においても、gets は危険なので fgets に置き換えるべきであるという指摘をよく見かけます。しかし、こうした仕様の違いを示さず、あるいは fgets を用いた場合の具体的なコードを示さず、単純に置き換えれば済むかのような発言は、特に C 言語の経験が浅いプログラマをさらに困惑させることになります。
学校の課題などで、文字列の入力そのものが本題ではないのであれば、とりあえず gets で手軽に入力処理を記述しておき、本題に注力するのは悪くありません。また、学校の課題ではなくても、コード片の実験であったり、その場限りの使い捨てプログラムであれば、理解した上で gets を使ってもやはり問題はありません。
最後に、fgets を用いて本格的に gets を置き換える場合の例を示すことにしましょう。
char s[5];
size_t n;
if (fgets(s, 5, stdin) == NULL)
/* エラー処理 */;
n = strlen(s);
if (s[n - 1] == '\n')
{
s[n - 1] = '\0';
}
else
{
int c;
do
{
c = getchar();
if (c == EOF)
{
if (feof(stdin))
break;
else
/* エラー処理 */;
}
}
while (c != '\n');
}
決してお手軽とはいえませんね。そして、上のコードではバッファに格納できなかった文字は捨てていますが、捨ててはいけない場合には、realloc でバッファを拡張しながら残りの文字を読み込む必要があります。
参考情報
トラックバック
ブックナビゲーション
- 技術情報
- Boost C++ Libraries メモ
- C++と組込み環境
- C++サンプル集
- C++テンプレート集
- C++プログラマのためのC言語入門
- C/C++迷信集
- [迷信] 'A'~'Z' の値は連続している
- [迷信] 0xe-0xe はゼロ
- [迷信] 1 バイトは 8 ビット
- [迷信] 2の累乗による割り算と右シフトは等価
- [迷信] FILE 型は構造体
- [迷信] abs は常に非負の値を返す
- [迷信] argv[0] はプログラム名
- [迷信] char 型は符号付き
- [迷信] double の出力書式は "%lf"
- [迷信] fflush で入力バッファをクリア
- [迷信] free でメモリを開放する
- [迷信] free に NULL を渡すとクラッシュする
- [迷信] gets は単純に fgets に置き換えられる
- [迷信] isalpha 関数の引数は char 型
- [迷信] new に失敗すると NULL が返る。
- [迷信] scanf ではバッファオーバーランを防げない
- [迷信] scanf でキーボードから入力
- [迷信] setjmp マクロの返却値は変数に代入できる
- [迷信] sizeof は定数式
- [迷信] void main(void)
- [迷信] とりあえず memset で初期化
- [迷信] アルゴリズム関数内で関数オブジェクトはコピーされない
- [迷信] オブジェクトの動的生成に失敗するとメモリリークする
- [迷信] コンストラクタから例外を送出してはならない
- [迷信] コンストラクタで自身をゼロクリア
- [迷信] コンパイラはプログラマの心を察してくれる
- [迷信] コンパイルエラーが出るのでアクセス指定子を修正
- [迷信] ソースコード中の即値を全廃せよ
- [迷信] ソースファイルの末尾に }
- [迷信] データ列のソートには qsort 関数を使うべし
- [迷信] プログラムは必ず main から始まる
- [迷信] 一重引用符の中には一文字しか書けない
- [迷信] 今どき int が 16 ビットの処理系なんて無い
- [迷信] 入力データ格納用配列のサイズは BUFSIZ
- [迷信] 割付けたメモリはプログラマが自分で解放しなければならない
- [迷信] 実数型とは浮動小数点型のことである
- [迷信] 引用符で囲んだヘッダ名はカレントディレクトリから探索する
- [迷信] 文字列から整数への変換には atoi
- [迷信] 構造体のタグ名は下線で始める
- [迷信] 構造体はクラスではない
- [迷信] 識別子に使える文字は英数字と下線のみ
- [迷信] 非局所オブジェクトは外部結合
- C言語再入門
- C言語徹底入門
- Drupal メモ
- TOPPERS 情報
- ライブラリ開発入門
- 分割コンパイルをきわめる
- 擬似プロセッサを作る
- 車輪の再発明
- 過去の情報

