Scanf
scanf(スキャンエフ)は、C言語の標準関数。ヘッダーファイル stdio.h で定義されている、書式付き入力関数である。
標準入力(大抵はキーボード)からの入力を、書式に従って変数に読み込む機能を持つ。標準出力関数のprintfと対比させて考えると分かりやすい。
ユーザーからの入力を受ける、ごく基本的な機能を持つにもかかわらず、後述するように異常入力(エラー)に配慮すると相応の手間がかかるため、テストプログラムや入門書を除いてはあまり使われない。
このファミリーの関数には、入力ストリームを指定できる fscanf や、メモリ上の文字列ストリームを入力対象とする sscanf などがある。
stdio.h内で以下の様に宣言されている。
printf と同様、第1引数のformatは、それに続く可変長の実引数の変換方法(書式)を指定する。また戻り値は入力(スキャン)に成功した入力項目の数が返される。
利用例を以下に示す。
scanf の変換指定は次の形式をとる。
% [代入抑止][最大フィールド幅][長さ修飾子]変換指定子
というコードがあり、 "ab"という入力があった場合 1文字目の 'a' は無視され 2文字目の 'b' という文字が代入される。
[ ] は例えば
というコードがあり、入力に "babaacdeabfghijabcef" という文字列が入った場合、str には "babaac" という文字列のみが入力され、残りの文字列は入力されずに終了する。strに代入されなかった、"deabfghijabcef"は入力ストリームに残る形となる。また[^ ... ]とした場合は逆に[ ]内の文字が入ってくるまで文字を読み込む。
例えば、
という場合、"ghetbceajk"と入力すると、str には"ghet"が代入される。上記と同様に、入力されなかった文字列は入力ストリームに保持される。
ソース:
出力結果の例:
fscanf は第1引数にFILEポインタを指定することで、標準入力からではなくファイルストリームから読み込む。C言語では標準入力は stdin というFILEポインタで表すので、fscanf(stdin, ...) は scanf と完全に同義である。
sscanf は第1引数に文字列へのポインタを指定することで、標準入力からではなく文字列ストリームから読み込む。後述するように scanf でエラー処理を実装しようとすると、エラーにならずに再度入力待ちになってしまうパターンがある。このため標準入力から数値を入力する場合には、直接 scanf を使わずにいったん fgets 関数等で文字列として読み込んでから sscanf で処理することの方が多い。
scanf は非常に簡潔に入力の制御が行えるため、C言語の入門書では必ずと言っていいほど登場する。しかしこの関数にはいくつかの問題点が指摘されている。ここではよく指摘される scanf の問題点とその回避方法について宣べる。
入力が文字(char)の場合、主にキーボードで入力の際に押されるリターンキーが無視できないという問題点がある。例えば
とした場合、通常なら3回入力待ちが行われることが期待されていると思われるが、実際には2回しか行われない(予期しない入力はここでは考慮しない)。これは最初の a の入力には入力された文字が代入されるが、このときストリーム上に改行コードが残されてしまい、次の b には a を入力する際に押下されたリターンキーの改行コードが代入されるためである。通常の %d や %s の場合改行コードは無視して入力を読み込むので問題にはならないが、 %c の場合は無条件にストリーム上の次のバイトを返すためこのような現象が発生する。これを防ぐには、
のように最初に空白を入れることで回避される。これは入力前に空白(改行コードも含む)を読み飛ばすことを意味している。ただしこの場合 a, b, c に半角スペースを代入することはできない。そのような場合としては
という方法がとられる。これは入力した際の改行文字を%*cで除去することを意味する。
scanf は空白とタブは改行文字と同じ区切り文字として扱われる。例えば、
という方法で文字列を読み込もうとしたとする。このとき入力文字列が "This is a pen" というものだった場合、実引数に入力されるものは "This" までである。これだけでも問題であるが、さらに問題なのは、残りの " is a pen" という文字列はストリーム上に残されてしまうことである。このため次に scanf を呼んだ場合、入力にかかわらず実引数に "is" という文字列を代入しようとしてしまうことになる。これを防ぐ手段としては
という方法がとられる。この場合は改行文字以外の文字列を全て a に代入するため、a には空白やタブも含めて文字列が代入される。なお前述における入力時の改行コードの取り扱いも考慮した場合は、
という記述になる。
他の C言語の文字列処理関数と同様に、scanf を使用する際にもバッファオーバーランが発生する可能性がある。例えば
というコードがあった場合、a に入力できる文字列は終了文字を除く、19バイトまでしか入力することが出来ず、それ以上の文字列が入力されるとバッファオーバーランが発生する。a の領域が十分であれば問題はないが、しかし入力されてくる文字列の長さを予測することは不可能であり、結局 a の長さをいくら大きくしても、さらにそれを上回る長さの入力が行われてしまえばバッファオーバーランが発生してしまう危険性がある。
この問題を防ぐ手段として一般的なのは最大フィールド幅を指定することである。上記の例の場合は
とすることで、aには最初の19バイトが読み込まれ入力を終了する。ただしこの場合は残りの文字列はストリーム上に残ってしまうので実際には以下のようにすることで対処される。
これは19バイト読み込みさらに改行文字までの文字列を空読みすることを意味する。
となるが、この場合はaに入る文字列が 19バイト以下の場合には、入力ストリームにやはり改行文字が残る。よって実際には、
のように入力終了後、改行文字を空読みするなどの方法がとられる。空白文字の取り扱いも考慮する場合
となる。
バージョン8.0 (2005) 以降のMicrosoft Visual C++コンパイラには、 という関数が用意されていて、バッファのサイズを指定することができる。scanf_s はC11規格で標準化されたが、実装は任意である。
scanf関数は、予期せぬ値が入力されると、その値を読み込まず、ストリーム上に残してしまう。例えば
としたコードがあった場合、入力が数字でなかった場合は scanf はストリームにその文字列を残したまま終了することになる。このため次に scanf が呼ばれたときはそのストリーム上のバッファが実引数に代入され、他の scanf にも異常な結果を返すことになる。例として 1 が入力されたら終了、それ以外はもう一度入力を求めるようなプログラムを考えてみる、もしこのプログラムを
というようなコードで記述した場合、数字以外の文字を入力してしまうと無限ループに陥ることになる。このような現象を防ぐ手段として scanf の戻り値を利用する方法がとられる。これは scanf は代入に成功した変数の数を戻り値として返すため、指定した実引数の数と scanf の戻り値が一致しないときに入力バッファをクリアすることで回避できる。例えば上記の例の場合は以下のようになる。
入力バッファのクリアはここでは文字列を空読みすることで実現している(入力バッファのクリア方法は状況によって異なる方法が考えられる)。なお主にウェブ上などで、標準入力のバッファクリアの方法に fflush(stdin) や rewind(stdin) とする方法が紹介されることがあるが、ANSI規格ではこれらの動作は未定義であり、正しい動作を保障するものではない。
この方法でエラー処理をする場合には上記の空白文字の取り扱いの関連で問題になることがある。例えば
のようなコードで、異常な入力があった場合エラー処理をしたいとする。これを
という方法で実現すると入力が "1" のみの場合、空白と改行は同一視されてしまうため、変数 "i" に 1 を入力して後、変数 "j"の入力待ち状態となる。もし、このような場合にもエラー処理を実行したい場合には、例えば以下のように一度文字列として読み込んで、その後 sscanf で読み込むことで実装すればよい。