質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

7121閲覧

オーバーフローを防ぎたいんですが、fgets()だと表示が崩れる。

strike1217

総合スコア651

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2017/12/04 08:35

セキュアなプログラムでは、ユーザー入力からのオーバーフローを防ぐのがかなり大切なことかな。と思います。

テストプログラムです。

C

1#include<stdio.h> 2#include<stdlib.h> 3#include<string.h> 4 5int main(){ 6 int num; 7 char **ptr; 8 9 printf("文字列は何個 : "); 10 scanf("%d", &num); 11 12 ptr = (char **)calloc(num, sizeof(char *)); 13 14 if(ptr == NULL) 15 puts("calloc error!!"); 16 else{ 17 int i; 18 19 for(i = 0; i < num; i++){ 20 char temp[128]; 21 22 printf("ptr[%d] :", i); 23 //if(fgets(temp, 128, stdin) == NULL) 24 // goto FREE; 25 scanf("%127s%*[^\n]%*c", temp); 26 27 ptr[i] = (char *)calloc(strlen(temp) + 1, sizeof(char)); 28 29 if(ptr[i] == NULL){ 30 puts("Error to calloc Second stage"); 31 goto FREE; 32 } else { 33 strncpy(ptr[i], temp, strlen(temp) + 1); 34 } 35 } 36 37 putchar('\n'); 38 for(i = 0; i < num; i++) 39 printf("ptr[%d] : %s\n", i, ptr[i]); 40 41FREE: 42 for(i = 0; i < num; i++) 43 free(ptr[i]); 44 free(ptr); 45 } 46 47 return 0; 48} 49

こちらはscanf()でオーバーフローを防いでいます。
正常に動作しオーバーフローは起きません。

[迷信] scanf ではバッファオーバーランを防げない
データの読み込み(scanf関数)

今回の場合、scanf()が複数回実行されるので、stdinバッファ内に文字列を残さないようにしています。

ここからが本題です。
scanf()の他にfgets関数を使う方法があります。
標準入力から安全に文字列を受け取る方法いろいろ

コメントの方にも書かれているのですが、

C

1fgets(temp, 128, stdin);

こちらを使うと表示が崩れます。

文字列は何個 : 2
ptr[0] :ptr[1] :fhdklafuadsf

ptr[0] :

ptr[1] : fhdklafuadsf

このように、標準出力が崩れてしまいます。
なぜでしょうか?
どうすれば解決できますか?
いろいろ試してみたのですが、どうもうまくいきません。

fgets()は入力の処理をしているのになぜ出力の方に影響が出ているんですか??
おまけに1回しか入力を受け付けていないという困った現象まで起きています。
あ、ちなみに、オーバーフローの方は防げているようです。

それと、scanf(), fgets() 以外にユーザー入力からのオーバーフローを防げる関数ってあるんですか?
(scanf_s()以外)

Linux 64bit gccです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

そもそも表示が崩れた原因は

scanfとfgetsを混ぜて使うと入力ストリームの状態が意図せぬ状態になってうまくいかない

元のコードでいえば、むしろ原因は先頭の scanf() にあると思います。

C

1 printf("文字列は何個 : "); 2 scanf("%d", &num);

ここで、例えば 12 と入力したとしましょう。実際は改行キーも叩くので、入力ストリームには '1' '2' '\n' という3文字が入力されます。scanf("%d", &num); は、そのうちの '1' '2' を読み出して num に 12 を代入します。
この時点で、まだ'\n' がストリームに残っていることに注意。この \n があるため、 一回目の fgets() の呼出しは、キーをタイプしなくても、自動的に空の一行が入力されてしまう、という現象だと思います。

だから scanf("%d", &num); の周辺に小細工を弄する手もあるでしょうが、私なら潔く scanf() を諦め、最初の数字入力も fgets() でやる手を選びます。こんな関数を紹介しましょう。

C

1char *myGets(char *buf, int size) 2{ 3 char *ptr; 4 5 ptr = fgets(buf, size, stdin); 6 if (ptr == NULL) // any error from fgets() 7 return NULL; // then, return error 8 9 ptr = strchr(buf, '\n'); // search '\n' in input 10 if (ptr != NULL) // found '\n' ! 11 *ptr = '\0'; // erase '\n' 12 13 return buf; 14}

strchr(buf, '\n') は、buf 文字列中に '\n' があるかどうかを検査します。即ち '\n' が無い場合もあります(その条件は fgets() の仕様を調べること)。目的は '\n' '\0' を '\0' '\0' にする、即ち改行文字を消してしまう事です。こうしておけば、

C

1 printf("文字列は何個 : "); 2 myGets(temp, BUFLEN); 3 sscanf(temp, "%d", &num); 4 // 途中省略 5 for (i = 0; i < num; i++) { 6 printf("ptr[%d] :", i); 7 if (myGets(temp, BUFLEN) == NULL) 8 goto FREE; 9 10

のような感じで動作を確かめられるはず。

P.S.
オーバーフローというより、バッファオーバーランと呼んだ方が良いのではないかな。オーバーフローとは、例えば unsigned char 型の変数で 200 + 100 を計算したら桁溢れを起こして 300 にならない、ような場合がふさわしい気がする。

投稿2017/12/04 16:33

rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yumetodo

2017/12/04 17:07

>即ち '\n' が無い場合もあります 面倒くさくなって私のコードではstrlenで文字列末尾を得て後ろから2文字について改行文字か判別して置換するようにしてしまった。
yumetodo

2017/12/04 17:35

>こんな関数を紹介しましょう。 それむしろC11で追加されたgets_sでは
rubato6809

2017/12/05 02:01

そうみたいですねw。ほとんどがC89止まりの作業なので、気づきませんでした。 ライブラリ関数のコードを読んで仕組みを理解する、さらに自分でコード化してみるのも勉強になりますよ>strike君
strike1217

2017/12/05 04:26

おお! なるほど!大変分かりやすいです。 やはり、fgetsとscanfは併用するとまずいんですね。 ありがとうございます。
strike1217

2017/12/05 04:31

C言語、細かいところがやけに難しいですね。 \nがストリームに残っていることに気づきませんでした。 はぁ〜〜〜。
yumetodo

2017/12/05 11:14

C言語の標準入出力はラスボスだってもうn回言ってる C++でもstd::getlineとopoerator>>を混ぜて使うと似たような問題に直面します
yumetodo

2017/12/05 11:15

ではC/C++以外の言語はどうかというとperlやRustではそもそも一行読み切ってからtrimするというのが定石になっているようです。
guest

0

改行文字のことをお忘れなんじゃないかと思われます。末尾の改行文字をNULL文字に置き換えてください。

なおC11でgets_sというものが追加されましたので、これは改行文字削除までやってくれます。


追記

そもそもscanfで数値入力受けるなとか他にも思うところがあったのでリファクタリングしました。
https://wandbox.org/permlink/wSRKNaoFwuM8bs1Y


再追記

rubato6809さんの回答見てて思ったけど、文字列は何個で入力したより少ない文字列が入力されてEOFが来て抜ける時、EOFが行頭にあるか否かで挙動が変わってしまう・・・

というわけで修正しました
https://wandbox.org/permlink/Yo0UjWNBiXHLtR5I

投稿2017/12/04 09:58

編集2017/12/04 17:52
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/12/04 10:34

ん?すいません。どこのことでしょうか??
yumetodo

2017/12/04 10:50

>scanf("%127s%*[^\n]%*c", temp); をコメントアウトしてその上をコメント外した場合の話と理解していますが、 >strncpy(ptr[i], temp, strlen(temp) + 1); ということは末尾2byteは \n \0 になるということです。
yumetodo

2017/12/04 10:52

まあそもそもなんtempなんていうバッファーに受けてまたコピーしているんだろうという思いでいっぱいですが(fgetsの戻り値とferrorの戻り値見て必要ならrealloc呼び出さないと長い行に対応できない
strike1217

2017/12/04 10:54

「バッファーに受けてまたコピーしているんだろうという思いでいっぱいです」それは自分も思いました。 これは本に載っていたサンプルなので、オーバーフローの部分だけ自分で書き換えたのです。
strike1217

2017/12/04 10:56

「末尾の改行文字をNULL文字に置き換えてください。 」 これは、つまり、 \n\0 を \0にしなさい。という意味でしょうか??
strike1217

2017/12/04 11:57

う〜〜ん。 ダメみたいですね。 うまく表示されないですね。 scanf() -> fgets()に変更しただけでなんでこうなってしまうんですかね・・・
strike1217

2017/12/04 11:58

そちらの方ではうまくできましたでしょうか??
yumetodo

2017/12/04 12:23

脳内コンパイラにかけていただけだったのでちょっとリファクタリングしてきます、お待ちを
strike1217

2017/12/04 13:15

fmfm・・・ 実際にやってみます。
strike1217

2017/12/04 13:21

・・・このコードはこの短時間で作り上げてしまったんですか??
strike1217

2017/12/04 14:11

おお!!できました。 すごい。 なんで、自分が作った方ではできなんでしょうかね・・・ 見比べて、原因を見つけてみます。
yumetodo

2017/12/04 15:02

>・・・このコードはこの短時間で作り上げてしまったんですか?? 21:33から作り始めて21:55くらいまで作ってましたね。fgetsやらferrorやらfeofの仕様をすっかり忘れてしまってman読みにいってたのでえらい時間がかかった。7分くらいで書けないと・・・。最近C書いてなかったからどえらい時間かかってしまった。
yumetodo

2017/12/04 15:04

全般にscanfとfgetsを混ぜて使うと入力ストリームの状態が意図せぬ状態になってうまくいかない印象。自分はその辺が面倒くさいので例外なく全部scanfで読み取ってしまう(scanf系が必要ならそこからさらにsscanf)
strike1217

2017/12/04 15:14

「scanfとfgetsを混ぜて使うと入力ストリームの状態が意図せぬ状態になってうまくいかない印象。」 ああ、やはりそうですよね。 なんとなくそうかもしれないと考えていました。 ・・・・ 随分きれいなコード書きますね。かっこいい。 失礼ですが、かなりできる方だったんですね。
yumetodo

2017/12/04 17:56

真面目にエラー対策していくとどんどんネストが深くなってしかもどうやってもきれいにならないC言語やっぱり嫌だ。RAIIと例外を私にくれ、という思い。 >随分きれいなコード書きますね。かっこいい。 ループをfor文で書くだけできれいになりやすい印象。あとは変数を逐次宣言して先頭にまとめないこと。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問