Rits Logo
立命館大学
情報理工学部
メディア情報学科

Last Update : Oct. 15, 2010

[section]書式付き入力関数

ここではscanf関数などの書式付き入力関数についてまとめてみました.

書式付き入力関数

scanf関数などの書式(フォーマット)付き入力関数は,指定されたフォーマットに従って,ファイルや標準入力からデータを読み込む関数です.基本的なフォーマットは以下の通りです.

%[*][WIDTH][SIZE]TYPE
		

'[',']'で囲まれた部分はオプションです.'*'は代入抑制を表し,これが指定されると,入力ストリームから読み込みは行われますが,代入されずに破棄されます.'WIDTH' には最大のフィールド幅を10進数で指定します.'SIZE'には,読み込むデータ型の大きさを指定します.そして,'TYPE'の部分にフォーマットを指定します.

フォーマットとして使用する代表的な変換指定文字は以下の表の通りです.

表:書式付き入力関数の変換指定文字
変換指定文字意味データ型
c1文字として入力文字型(char)
d10進数として入力整数型(int, long)
x16進数として入力整数型(int, long)
o8進数として入力整数型(int, long)
f浮動小数点値として入力浮動小数点型(float, double)
s文字列として入力文字型配列(char *)

ただし注意点として,double型の入力の場合,変換指定文字の前に,'SIZE'を表す "l"(エル)をつけて,"%lf" とする必要があります.同様に,long型の入力にも変換指定文字の前に "l"(エル)をつけて,"%ld" のようにします.

簡単な実験として,以下のプログラムを作成し,実行してみました.入力としては,"1 2 3" という文字列を,合計3回与えています.実行結果をその下に示しますので,注意してscanfの実行結果を確認してください.

scanf確認用プログラム

#include <stdio.h>

int main(int argc, char* argv[]) {
    int i ;
    float f ;
    double d ;

    scanf("%d %d %d", &i, &f, &d) ;
    printf(%d, %f, %f\n", i, f, d) ;

    scanf("%f %f %f", &i, &f, &d) ;
    printf(%d, %f, %f\n", i, f, d) ;

    scanf("%lf %lf %lf", &i, &f, &d) ;
    printf(%d, %f, %f\n", i, f, d) ;
}
		

実行結果

[ 11 ] <coi2t001:/homer/is/XXX/someone> ./test
1 2 3
1, 0.000000, 5.384187
1 2 3
1065353216, 2.000000, 5.384187
1 2 3
1073741824, 0.000000, 3.000000
		

scanf関数の問題点

scanf関数は,非常に便利ですが,いくつかの問題点が指摘されています.

バッファオーバーラン(脆弱性)

例えば,次のようなプログラムがあったとします.

char str[128] ;

scanf("%s", str) ;
		

このとき,文字列の最後には必ずヌル文字('\0')が必要ですので,scanf関数で読み込むことができる文字数は127文字になります.つまり,128文字以上の入力があったとすると,配列の範囲を超えてしまうため,バッファオーバーランが発生します

この問題を回避するために,最大のフィールド幅を設定する方法があります.例えば,以下のようにプログラムを書けば,標準入力から与えた文字列の最初の127文字のみが読み込まれます.これによりバッファオーバーランが防げます.

char str[128] ;

scanf("%127s", str) ;
		

ただし,このままだと入力ストリーム上に,入力文字が残ってしまいますので,実際には以下のようにして,改行文字を含む不要な文字を読み飛ばします.

char str[128] ;

scanf("%127s%*[^\n]%*c", str) ;
		

簡単に意味を説明すると,"%*[^\n]" は,改行文字('\n')以外('^'の効果)の文字を読み込むが,代入は抑制する('*'の効果),という意味です.また,"%*c" は,末尾に残った改行文字('%c'による1文字)を読み込むが,代入は抑制する,という意味です.

空白を読み込まない

scanf関数を使って文字列を読み込む場合,変換指定文字として "%s" を使用します.ところが,この指定では,例えば,"This is a test." などの空白を含む入力があった場合に,空白で区切られた最初の "This" のみが読み込まれます.

これだけならまだましですが,入力した文字列の残りの部分 "is a test." は,入力ストリーム中に残ります.そのため,次回,scanf関数を呼び出した際に,"is" の部分が入力として読み込まれてしまいます

このような場合,以下のようにプログラムすれば,"This is a test." が丸ごと読み込まれます.

scanf("%[^\n]", str) ;
		

"%[^\n]"は,改行文字('\n')以外('^'の効果)の文字を読み込む,ということを意味します.実際には,最後に入力される改行文字を破棄するために,下記のように使うのが一般的です.

scanf("%[^\n]%*c", str) ;