scanf
View Outline
scanf が予期せぬ動作をする例
(自分が作ったわけではないが)ちょっとしたツールで痛い目に遭った。 下記がそのツールのスケルトン。 やっつけ仕事チックなロジックであるが、皆やりそうな造りである。 もちろん scanf は使ってはいけない(gets & sscanf がセキュアな造り)のだが、それ以上の悲劇が待っていた…
1 #include<stdio.h> 2 3 int main(int argc, char *argv[]){ 4 5 int kind = 0; 6 while(kind != 99){ 7 printf("*** なにかのツール ***\n" 8 "1:表示 2:クリア 99:終了\n" 9 "> "); 10 scanf("%d", &kind); 11 switch(kind){ 12 case 1: 13 /* 表示処理 */ 14 break; 15 case 2: 16 /* クリア処理 */ 17 break; 18 case 99: 19 break; 20 } 21 } 22 return(0); 23 }
このツールを起動してみる。
$ hogetool *** なにかのツール *** 1:表示 2:クリア 99:終了 > 1 *** なにかのツール *** 1:表示 2:クリア 99:終了 > 2 *** なにかのツール *** 1:表示 2:クリア 99:終了 >
ここで爆弾を発動させてしてしまった。 試験を急いでいたので、99 を入力せずに端末エミュレータを閉じてしまったのである。
するとどうなるか。
UNIXでは、親プロセスを閉じると子プロセスもいなくなるように思われているが、それは正確ではない。 実際には、「孤児プロセス」(Orphan Process)として init プロセスの子プロセスとして残る。
確かに現象が発生していたとき、ps コマンドで見たらこのツールのオーナーは一般ユーザーにもかかわらず、親プロセスIDは 1 だった。
そして、標準入出力のファイルディスクリプタが閉じられることにより、10 行目の scanf は戻り値 EOF を返して kind は未定義(普通は、前値保持であろうが)となる。 上記ソースと入力の組合せにおいて 10 行目の入力待ち状態で孤児プロセスとなると、kind に 2 が保持された状態で無限ループになってしまったのである。 つまり、誰にも気づかれることなくテーブルをクリアし続けたという訳。 枠組みを壊さずに対策するなら以下であろう。
10 if(scanf("%d", &kind) == EOF) break;
実際は、この見えない爆弾をセットした計算機と、クリアする先の計算機が別であったため、異常動作する計算機を再起動してもクリアされ続け、複雑怪奇な現象になったわけ。 皆さんも気をつけましょう。