補足説明 「キーボード入力と,入力バッファ」

 Copyright(C) 15Feb2003 coskx


【1】このページについて
「キーボード入力」が「ユーザプログラム」にたどり着くまでには「入力バッファ」を通過する。そのため,scanf,getchar混在のプログラムで思わぬ動作をしているように感ずることがある。この不可思議な動作を解説することがこのページの目的である。

【2】「キーボード入力」が「ユーザプログラム」にたどり着くまで
キーボード入力する時には次の経路でユーザプログラムにキーボード入力が到達する。
(1)キーボード入力する時,コンソール画面の1行内なら「BS」キーを使って,入力の修正が出来る。ここまではキーボード入力場面である。正しく文字列を入力できたら人間は最後にリターンキーを押す。リターンキーが押されたら,画面上の一行は次の「入力バッファ」に送られる。
(2)コンソール画面の1行が「入力バッファ」に入り,ユーザプログラムのコンソール入力関数の求めにより,バッファ内の文字列をユーザ関数に渡す。
(3)ユーザプログラム中で,関数getchar,gets,scanfは,「入力バッファ」から文字列を受け取り,それぞれの機能で変換しながら関数を呼び出した側の変数に格納する。

例1 getcharにおける入力バッファの作用
int mychar;の宣言の元に
maychar=getchar();
のところが実行される時のことを考えよう。
ここにさしかかると,キーボード入力待ちになる。ここで人間が「A」のキーを打ってもまだキーボード入力場面である。(だから,まだBSキーによる訂正が可能である)次にリターンキーを打つと「A」と改行コード「'\n'」が入力バッファに送られる。そしてgetcharが入力バッファから1文字受け取るので,呼び出し側の変数mycharに'A'が受け取られる。しかし,まだ入力バッファ中に改行コード「'\n'」が残っている。だから次にgetcharで1文字読み込もうとすると,入力バッファ中の改行コード「'\n'」が入力される。
'\n'は十六進数で0aである。

List 2.1 getcharの怪

#include <stdio.h>

int main()
{
    int mychar;
    do {
        mychar=getchar();
        if (' '<mychar) {
            printf("mychar=%02x %c\n",mychar,mychar);
        } else {
            printf("mychar=%02x\n",mychar);
        }
    } while (mychar !='\n');
    return 0;
}

起動後
ABCDEF
[ENTER]
と入力して,以下の結果を得た。
入力バッファの影響を考えながらプログラムを追うこと
ABCDEF[ENTER]
mychar=41 A
mychar=42 B
mychar=43 C
mychar=44 D
mychar=45 E
mychar=46 F
mychar=0a


List 2.2 getcharの怪(左右のプログラムでは青字の部分が異なる)

#include <stdio.h>

int main()
{
    int mychar;
    do {
        mychar=getchar();
        if (' '<mychar) {
            printf("mychar=%02x %c\n",mychar,mychar);
        } else  {
            printf("mychar=%02x\n",mychar);
        }
    } while (mychar !='\n');
    return 0;
}

#include <stdio.h>

int main()
{
    int mychar;
    do {
        fflush(stdin); /*入力バッファのクリア*/
        mychar=getchar();
        if (' '<mychar) {
            printf("mychar=%02x %c\n",mychar,mychar);
        } else  {
            printf("mychar=%02x\n",mychar);
        }
    } while (mychar !='\n');
    return 0;
}

起動後
A[ENTER]
B[ENTER]
C[ENTER]
[ENTER]
と入力したかったが,「A[ENTER]」だけで終了してしまった。
失敗
入力バッファの影響を考えながらプログラムを追うこと

起動後
A
[ENTER]
B
[ENTER]
C
[ENTER]
[ENTER]
と入力。

入力バッファの影響を考えながらプログラムを追うこと

A[ENTER]
mychar=41 A
mychar=0a

A[ENTER]
mychar=41 A
B[ENTER]
mychar=42 B
C[ENTER]
mychar=43 C
[ENTER]
mychar=0a


この不具合はgetchar()が入力バッファ内に残っている'\n'を読み込むことが原因である。
ここにある右側の例のようにgetchar()を用いる直前にfflaush(stdin)を書いておくと安全である


例2 scanfにおける入力バッファの作用
scanfでも入力バッファから文字列を受け取り,書式%dなどでは数値に変換して変数に格納する。入力バッファ中の終わりまで文字列を使っていない場合は次のscanfなどの関数へ渡される。

List 2.3 scanfの怪
 同じプログラムでも実行時での数値の与え方によって微妙に異なる実行結果
 しかし,特に困らない

#include <stdio.h>

int main()
{
    int a[3],i;
    for (i=0;i<3;i++) {
        scanf("%d",&a[i]);
        printf("a[%d]=%d\n",i,a[i]);
    }
    return 0;
}

数値を3回に分けて与えた実行結果 3つの数値を1行に書いた時の実行結果

123[ENTER]
a[0]=123
456[ENTER]
a[1]=456
789[ENTER]
a[2]=789

123 456 789[ENTER]
a[0]=123
a[1]=456
a[2]=789

例3 scanfとgetcharを使ったとき
入力された正整数の二乗を求めるプログラムを作る。複数の数の二乗を求めたいので1つ計算し答えを書くたびに,別の数について実行するかどうか問合せるようにしたい。そして次のような動作にしたい。
正整数を入力 →12
x=12 x*x=144
別の数でやり直しますか (Y/N) →y
正整数を入力 →16
x=16 x*x=256
別の数でやり直しますか (Y/N) →n

List 2.4 繰返しを問合せるプログラム

#include <stdio.h>

int main()
{
    int x;
    int ch;
    do {
        printf("正整数を入力 →");
        scanf("%d",&x);
        printf("x=%d x*x=%d\n",x,x*x);
        printf("別の数でやり直しますか (Y/N) →");
        ch=getchar();
    } while (ch=='Y'||ch=='y');
    return 0;
}

#include <stdio.h>

int main()
{
    int x;
    int ch;
    do {
        printf("正整数を入力 →");
        scanf("%d",&x);
        printf("x=%d x*x=%d\n",x,x*x);
        printf("別の数でやり直しますか (Y/N) →");
        fflush(stdin); /*入力バッファのクリア*/
        ch=getchar();
    } while (ch=='Y'||ch=='y');
    return 0;
}

正整数を入力 →12
x=12 x*x=144
別の数でやり直しますか (Y/N) →

(yを入力する前にプログラムが終了してしまった。失敗)
scanf終了後,入力バッファ内部に残っていた'\n'が
getcharによって読み込まれた結果,計算結果を表示して
すぐに終わってしまう。

正整数を入力 →12
x=12 x*x=144
別の数でやり直しますか (Y/N) →y
正整数を入力 →16
x=16 x*x=256
別の数でやり直しますか (Y/N) →n

(入力バッファをクリアしたので成功)

この不具合はgetchar()が入力バッファ内に残っている'\n'を読み込むことが原因である。
ここにある右側の例のようにgetchar()を用いる直前にfflaush(stdin)を書いておくと安全である

まとめ

直前のgetchar(),scanf()の影響で,後に続くgetchar(),gets()が'\n'を読み込んでしまい,影響を受けている場合は
影響を受ける側のgetchar()やgets()の直前にfflaush(stdin)を書いておくと不具合を避けることができる。