これも根が深い迷信です。この迷信を根拠に、scanf は使うべきではないという人も大勢います。おそらくこういうことでしょう。
char s[10]; scanf("%s", s);
確かにこれでは、ユーザーが10文字以上入力した時点で未定義の動作を引き起こしてしまいます。しかし、これは書式指定が不適切なために発生する脆弱性であって、scanf の問題ではありません。
バッファオーバーランを回避するには次のようにします。
char s[10]; scanf("%9s", s);
これで差し当たっての問題はなくなりました。
さて、熱心な迷信の信者は、何とかして scanf の名誉回復を阻むために、巧みに論点のすり替えを行いながら、scanf を貶めようとすることでしょう。
例えば、上記のように文字列を読み込めばバッファオーバーランは防げるかもしれないが、格納されなかった文字、あるいは改行文字がバッファに残ってしまい、後で悪さをすると。
そんな場合は、こうすれば解決します。
char s[10]; scanf("%9s%*[^\n]%*c", s);
※ 注意! ここはやや誤解を与える表現になっていました。コメントを参照してください。
scanf 系の関数は、文字列を読み込むことに限れば、fgets なんかよりずっと柔軟な処理ができます。要は、書式指定を完全に把握する気があるかどうかの問題です。
ところで、先ほど「文字列を読み込むことに限れば」と敢えて書きました。というのも、文字列を読み込む以外、例えば整数や実浮動小数点数を読み込む場合、scanf 系関数には意外に知られていない問題点があるからです。これは、fscanf でも sscanf でも同じことがいえます。
scanf 系の関数では、整数や実浮動小数点数を読み込む際に、オーバーフローやアンダーフローが発生しても検知することができません。実引数で指定した格納先の型で、入力した数値を表現できない場合の動作は未定義なのです。
scanf でバッファオーバーランを防げないと盲信している人は、数値の入力には脊椎反射的に fgets と sscanf で置き換えるという選択をすることが少なくありません。しかし、先に書いたような問題があるため、そうした方法は安全ではありません。ですから、
char s[100]; int x; fgets(s, 100, stdin); if (sscanf("%d", &x) < 1) { /* エラー処理 */ }
ではなく
char s[100]; long t; int x; char *endptr; fgets(s, 100, stdin); errno = 0; t = strtol(s, &endptr, 10); if (errno != 0 || *endptr != '\n' || (t < INT_MIN || INT_MAX < t)) { /* エラー処理 */ } x = t;
とした方がよいのです。
最後の方は、かなり横道にそれてしまいましたが、scanf
系関数に関連することですのでご容赦ください。
バッファオーバーランに関しては、IPA のセキュア・プログラミング講座の記事も参考にしてください。
ブックナビゲーション
- 技術情報
- 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
- [迷信] 構造体のタグ名は下線で始める
- [迷信] 構造体はクラスではない
- [迷信] 識別子に使える文字は英数字と下線のみ
- [迷信] 非局所オブジェクトは外部結合
- C99関数・マクロ・前処理スクリプト集
- C言語再入門
- C言語徹底入門
- Drupal メモ
- TOPPERS 情報
- ベターCとしてのC++
- マイコン メモ
- ライブラリ開発入門
- 分割コンパイルをきわめる
- 擬似プロセッサを作る
- 象の卵を探して...
- 車輪の再発明
- 過去の情報
補足説明
は、10文字以上文字列を入力した場合を想定しています。
実際にこの記述を行った場合、9文字以下しか入力しなければ改行文字が残ります。
9文字以下にも対応するには、例えば下記のようにします。
面倒ですが、
fgets
を用いても、のようにせざるを得ませんから、大差はありません。
もちろん、行中の余計な文字を読み飛ばすだけですので、
でもよいでしょう。
なお、改行無しで EOF が発生する場合や I/O エラーに備えるためには、もう少しエラーチェックが必要です。
scanf("%s ",
scanf("%s ", value);
%sの後にスペースを入れると、改行空白の類は全て無視されるようになりますが、
それではダメでしょうか?