オーバーフロー対策について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら

オーバーフロー対策について

#1

投稿記事 by » 16年前

以前リバーシの作成を通してオーバーフローの存在を大体理解したつもりでいましたが、改めて作成したリバーシのオーバーフローとなっている部分に対策を施そうと調べてみたのですが、少々分からない事が出てきました。
色々なサイトを参考にしたため、私自身も少々情報を整理し切れず混乱していますが、出来る限り何が分からないのか説明していきたいと思います(主にwikipediaのscanfの項目についての話です)
表現がおかしい所もあると思いますが、よろしくお願いします。

1.getchar()について。
まずgetcharはキーボード(標準入力)から一文字入力するというのは理解できています。
ユーザーが入力をするまでその先の処理には進まず、待機していると思うのですが、オーバーフローした際のバッファに残されてしまった文字を捨てるためにgetcharを用いて空読みをするという方法があったのですが、この際、
//ラインバッファ処理
	while( 1 ){
		c = getchar();
		if( c == EOF || c == '\n' ){
			break;
		}
		printf( "%c\n", c );
	}
といった具合にバッファを空にしていくのですが、何故getchar()で入力待ちが起きないのか? というのが疑問です。
バッファが空でない場合は入力待ちが起きず、空なら入力待ちが起きることを確認しているので「そういうものだ」と割り切ってしまえばそれまでなのですが、一応知っておきたいと思った次第です。

2.%*s
scanf()に関してなのですが、

scanf("%*s");

というものがどういった事をしているのかがよく分かりません。
scanf("%*c%c", c);
といった場合には、一文字目は無視され二文字目が変数cに代入されるというのは分かります。
ではそれが%*sになるとどうなるか、という点ですが確認した限りでは"文字が入力されたらそれ以降は無視"といった感じのような気がしました(数パターンの実行結果から予想しただけのものなので恐らく違うと思いますが。。。)
話がちょっとごちゃごちゃしてきそうなのでオーバーフローと%*sの関連性について述べると、例えば以下のような処理をした場合(色々省いています)、
while(1){
		printf("メニューを選んでください(入力は数字で)\n");
		printf("0:最初から  1:続きから 2:終了する\n");
		scanf("%d", &select);
		getchar();
	if(select == 0){
		stone[3][4].flag=black;
		break;
	}
ここで仮に"a"と入力しただけで、無限ループに入ってしまいます。
そこで、Wikipediaを参考に、
while(1){
	printf("メニューを選んでください(入力は数字で)\n");
	printf("0:最初から  1:続きから 2:終了する\n");
         if ( b=scanf("%d", &select) != 1){
     		scanf("%*s");
		printf("%d\n", select);
	}
	if(select == 0){
		stone[3][4].flag=black;
		break;
	}
としてみたのですが、"6y"や"5t"といった「数字+文字」の入力をすると一回余分にループしてから入力待ちになる、という状態になります。
そもそもまずscanf("%*s")がどういった処理なのかがよく分かっていないのでその点の解説あるいは参考になるページなどありましたらお教え頂けれると幸いです。

以上なのですが、正直まだまだ分からない所が多く、むしろどこが分からないのか分からないという状態なのでまだ質問事項が出てくるかもしれませんが、とりあえずまずはこの2つを解決したいと思います。
分かりにくい質問だとは思いますが、よろしくお願いします。


コンパイラ:BorlandC++ Compiler
開発環境:CPad for Borland C++Compiler
OS:Windows XP


追記:
int main(void){
	char c[4];
	int no=0;
	
	scanf("%s", c);
	scanf("%*s");
	printf("%s\n", c);
	
		
	return (0);
}
・結果
dvgdlgm43
43e
dvgdlgm43
-- Press any key to exit (Input "c" to continue) --

これの実行結果を見る限り、scanf("%*s")はどうも文字を無視するという訳でもなさそうですね。
バッファに残っている文字列を空にしていく、というわけでもなさそうですし。。。

Justy

Re:オーバーフロー対策について

#2

投稿記事 by Justy » 16年前


>1 getchar()について。
>何故getchar()で入力待ちが起きないのか?

 getcher()は入力ストリーム(この場合はキーボードの入力)から1文字ずつ
“取り出す”関数です。
 なので取り出せるなら直ぐに処理は返ってきますが、取り出せないなら
取り出せるまで(入力されるまで)待っているわけです。

 入力ストリームの終端は EOFか改行コードになりますので、
そのチェックを入れることで、ストリームが空になったことを検知することで、
getcher()が呼ばれて入力待ちになるのを防いでいます。



>scanf("%*s");
>というものがどういった事をしているのかがよく分かりません

 ストリームに文字列が残っていればそれを読みとって捨て、
残っていなければ入力を待ち、受け取った文字列を捨てます。

 %sであれば、区切りまでの文字列を入力ストリームから読みとり、
バッファに格納しますよね?
 %*sは %sで読みとられる分の文字列を入力ストリームから読みとって捨てます。
 ただそれだけです。

 %*sを %sに変えて適当な別のバッファにその中身を入れてみると
「どんな文字が捨てられるのか」わかるのでその挙動を調べやすいかと思います。



>、"6y"や"5t"といった「数字+文字」の入力をすると一回余分にループしてから入力待ちになる

 それはそうです。
 "6y"と入力すると 最初の scanfは %dなので、6の数字だけを読みとります。
 となると "y"はストリームに残ったままになりますので、
whileループで再度 %dの scanfが実行されたとき、数字ではないので
読みとりに失敗します。
 その結果 ifは真になり、{}内の scanf("%*s")によって、"y"が
クリアされるという流れになります。

 こういう流れになっていますので "6t z"と入力すると一回ではなく
二回余計に回ることが理解できますでしょうか。
 %sや %*sは終端か空白などの区切りまでしか読まないので、
1回目の if{}内の scanf("%*s")で tが消え、2回目で zが消えます。


 ちなみに、 if ( b=scanf("%d", &select) != 1)ですが、
これは scanfの結果 !=1の比較結果を bに入れているように見えますが、
それで正しいですか?



>追記:

 この追記のプログラムはおかしいです。
 配列 cは4つしかないのに、入力文字は4文字以上ありますので、
バッファオーバーランを起こしているのではないでしょうか。

 仮にそれがなかったとした場合、この追記のプログラムは
最初の %sで入力待ちになります。
 "dvgdlgm43"が入力されたらそれらが全て cに入り、ストリームは空に
なります。
 そこですかさず、%*sの scanfが実行されると捨てるものがないので、
入力待ちになります。

 代わりに "dvgdlgm43 aaa"と入力してみて下さい。
 %*sによって "aaa"が読み捨てられるはずです。

Re:オーバーフロー対策について

#3

投稿記事 by » 16年前

Justyさん、ご回答ありがとうございます。
大学の講義中に頂いた回答を見ながら色々考えていると、分からなかった部分がだいぶ理解出来ました。

まずgetcharに関して、といよりもそれ以前の話なのですが、今までキーボードからの入力は直接変数に代入されるという感覚でいたのですが、
どうもキーボードから入力された文字はバッファに格納され、
getcharやscanfはバッファに格納されている文字を代入する関数、
という感じなのだと思いました。つまりバッファに何らかの文字が残っていれば代入してしまう、
という事なのですね。
例えるなら黒板に書かれた文字列をノートに書き写す、という感じでしょうか(そして書き写した後黒板の文字は消える)。
g今までgetcharは入力を受け付けるという考え方をしていたため、
1文字ずつ取り出す関数と言われると非常に納得出来ました。

次に%*sですが、おっしゃられた通り%sに変えて確認してみた所、すぐに理解することが出来ました。
目に見える形で確認してみると、確かに"捨てる"か"代入するか"だけの違いですね……。

「数字+文字」の入力についてですが、上記の事が理解できるとすんなり分かりました。
%sでの確認方法を教えて頂いたので、その方法で確認することが出来たのも大きかったです。

>>scanfの結果 !=1の比較結果を bに入れているように見えますが、
まさしくおっしゃる通りです。
scanfが代入に成功した変数の数を返すということで、
なら実際ちゃんと値を返しているのかを見たかったため変数bにscanfの戻り値を代入しています。
ここに載せる際には必要のないもので消しておけば良かったのですが、忘れていました^^;

追記のプログラムに関してですが、てっきり配列を超えた入力分はバッファに残るものだと思っていたため、
その後のscanf("%*s")がバッファを空にしてくれるものだと思っていました。
そもそもこちらの掲示板で以前似たような質問をしているのにそれを忘れていました、、、
試しに「abcdefgh」と入力し、c[6]を表示させてみるとしっかり「g」が入っていました(そもそも「問題が発生したため終了します」的なメッセージが出てくるのですが)。

以上の事を踏まえ、オーバーフローが発生しない(と思われる)ように改良したプログラムを後学のため(にはならないと思いますが)一応載せておきます。
while(1) { 
		printf("メニューを選んでください(入力は数字で)\n");
  		if(scanf("%d", &i) != 1){
                                     //改行文字以外の文字列を全て空読み。
				scanf("%*[^\n]");
                   //↓空白を考えない場合
				//scanf("%*s");
	 			continue;
  		}
  		if(i == 1){
  			   break;
  		}
		scanf("%*[^\n]");
                  //scanf("%*s");
  		printf("str=%s\n", str);
		printf("i=%d\n",i);
	}
同じような処理を2箇所に書いているので無駄な部分があると思いますが、
異常な入力は受け付けないようにはなっていると思います。

とりあえず疑問は恐らく全て解決しました。
分からなかった一部分だけでも分かると、連鎖するように次々と理解できたので楽しかったです^^;
改めて、アドバイスを下さったJustyさん、ありがとうございました。

Justy

Re:オーバーフロー対策について

#4

投稿記事 by Justy » 16年前


>とりあえず疑問は恐らく全て解決しました。

 それは何よりです。




>なら実際ちゃんと値を返しているのかを見たかったため変数bにscanfの戻り値を代入しています

 えーと、今のコードですと、「scanfの戻り値」を bに、ではなく
「scanfの戻り値を 1と !=で比較した結果」を bに入れています。
 というのも代入の =と比較の !=では !=の方が優先順位が高いので先に実行されてしまうのです。

 なので、意図通りの動作にするには
b=scanf("%d", &select) != 1
ではなく
(b=scanf("%d", &select)) != 1
でなくてはならないかと。

BohYoh.com【C言語講座】演算子一覧表
ttp://www.bohyoh.com/CandCPP/C/operator.html

Re:オーバーフロー対策について

#5

投稿記事 by » 16年前

実行して確認してみた所、「3」を入力するとbに0が代入されていました。
なんだか色々と勘違いしていました……。

優先順位については大雑把にしか把握していなかったため、
ご紹介して頂いたページも大変参考になりました。
ありがとうございます。

閉鎖

“C言語何でも質問掲示板” へ戻る