C言語関係掲示板

過去ログ

No.70. gets()は使わないほうがいい


キーボードで数字を入力して、それを画面に出力したいのですが、
終了の仕方をリターンキーを押すと終了するようにするためには、
どのようにすれば良いのでしょうか?

int main(void)
{
int a[100], count, temp, i;

count = 0;
while (1) {
scanf("%d", &temp);
if (temp == -1)
break;
a[count] = temp;
count++;
}
i = 0;
while (i < count) {
printf("a[%d]..%d\n", i, a[i]);
i++;
}
return 0;
}
-1を押すと終了できる所までは、できるのですけど、
リターンキーを押せば終了にしたいのです。
宜しくお願いします。


こんにちは、ともじです。

> キーボードで数字を入力して、それを画面に出力したいのですが、
> 終了の仕方をリターンキーを押すと終了するようにするためには、
> どのようにすれば良いのでしょうか?

まず文字列としてキー入力し、空文字(いきなりリターンキーが押されると空文字
が入力されます)ならbreak。そうでなければatoi()で数値変換すればよいのでは
ないでしょうか。

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
        int a[100], count, i;
        char temp[20];

        count = 0;
        while (1) {
                gets(temp);
                if (temp[0] == '\0')
                        break;
                a[count] = atoi(temp);
                count++;
        }
        i = 0;
        while (i < count) {
                printf("a[%d]..%d\n", i, a[i]);
                i++;
        }
        return 0;
}


ども。

ちょっとだけ(アゲアシトリ?!)。

gets()は使わないほうがいい関数の部類に入ります。
用意したバッファがあふれる場合があり、それを防ぐ
ことができないからです。代わりにfgets()を使った
ほうがいいかと。fgets()をgets()の代わりに使うと
stdinを指定したり、取り込んだ文字列の最後に'\n'
がついたりと、ちょっと面倒かもしれませんが、まあ
gets()よりは無難では(テスト用はともかくとして)。
# なお、これを悪用して過去に多くのクラックが行わ
# れています

ついでに。
どこかでscanf()も使わないほうがいいという記述を
見かけるかもしれませんが、これも似たような理由、
期待している入力と形式が異なった場合にエラー処理
ができない、ということによるものです(と、多分
そこには書いてあります)。scanf()はちゃんとしたとこ
では、fgets()とsscanf()に置き換え、sscanf()の
返り値をチェックするとちょっと幸せになれるかも。

もひとつ。もとのプログラムについて。
2つ目のループ(while)はforで書き直せます。
書き直しても実行結果は変わりませんが、
readability(可読性、読みやすさ)は上がります。
forとwhileの使い分けは、forのフローチャートを
考えて、処理に適合するかを検討すればよいかと。
個人的な経験から言うと、ループ前に反復回数が
わかっている場合はfor、そうでない場合はwhile
で書けるような気がします。

では。


どうも有り難う御座いました。

それと、gets(),fgets()のことなんですけど、

gets()は、fgets()では第2引数である「サイズ」を受け取らないため
配列よりも長い行の入力があると、構わず配列の所まで書き込んで、呼
び出し元の配列を壊してしまう。

となっていて、あまり使われていないようなんですけれど、例えば、
int main(void)
{
    char buf[1024];
   
    fgets(buf, 1024, stdin);

    printf("buf..[%s]\n", buf);

    return 0;
}

とした場合は、bufのサイズが受け取られて、
int main(void)
{
    char buf[1024];

    gets(buf);

    printf("buf..%s\n", buf);

    return 0;
}

とした場合では、bufのサイズが受け取られないということなのでしょうか?

どちらを実行しても正しく表示されて、よく理解ができません。
最初にbuf[1024];と宣言していることと、混同していてどのように理解すれば
良いのかご教授願えないでしょうか?
宜しくお願いします。


>用意したバッファがあふれることがあり・・

バッファの内容が、OSに渡されるタイミングは、
バッファがいっぱいになったとき。
改行文字が来たとき。
(scanf()のような)入力要求があったとき。
と本に書いてありました。

あふれるということは、上で書いたbufの中身を超えてしまうと
いうことだと解釈しているのですが、今一つよくわからないです。

また、引用した本は入門の本のため初心者に分かりやすく書いてあるのかも知れません。
よく説明し切れていないのですけど、fgetsが上のbufのサイズを持つことで、何をしているのか、分からないのです。
どう理解したらよいのか、教えてください。


ども。

えと。

まず。
「バッファ」は一般名詞です。特定のプログラムが取り扱う
特定のメモリ領域を指すものではありません。

で。

>>用意したバッファがあふれることがあり・・

これはプログラマが扱うバッファ(例ではbuf[])を
さしています。 ...buf1とします

>バッファの内容が、OSに渡されるタイミングは、
>バッファがいっぱいになったとき。
>改行文字が来たとき。
>(scanf()のような)入力要求があったとき。
>と本に書いてありました。

これはOS(正確にはシェルかな?)が扱うコマンドラインの
バッファをさしています。 ...buf2とします
# 元の文を見ないとわかりませんが、上記の引用部はちょっと
# 変な気がします(入力バッファと出力バッファを混同してる?)

buf1のサイズと入力の長さ次第では、buf2はあふれなくても
buf1があふれる可能性があります。

buf1があふれた場合、おそらくbuf1が割り当てられている領域
の後ろにそのままあふれた分が書かれます(そこが本来どういう
目的で使われているかは関係なく!)。また、そのことを検出する
方法はC言語にはありません。運がよければ問題なく動くかも
しれませんが、いつでも問題なく動くことを期待してはいけません。
# こういうのを運がいいというのかどうか。。

ということで、使っていい領域をちゃんと管理するためにfgets()を
使うことになります。

なお、「例外」というしくみをもつ、C以外の言語では、実行時に
バッファあふれを検出することができるものがあります。
# でもC言語では効率至上主義で、かつ、プログラマがミスをしない
# ことが前提に。。って、どっかで書いたかな。。。

では。

p.s. 用意するバッファのサイズを#defineしておくといいかも。


ども。

buf[]のサイズを4とか8とかにして、それより長い入力を
入れてみてください。また、buf[]のほかにも変数を宣言して
それらの内容が破壊されるかどうかを確認してみてください。
# 確認する前に落ちる可能性がありますが

では。


前から疑問に思ってたんですけど、fgetsってなんで改行文字まで取り込んじゃうんでしょう?
あれ邪魔でしょうがないです。
getsにfがついてるだけなのにそういう細かいとこで動作が変わってるってのも統一性がなくてなんかヘン。
なんか深い事情でもあったんでしょうか?

fgetsを使うときって下記みたいなコードでいいんでしょうか?
いつも面倒でgets使っちゃうんで自信ないんです。
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf)-1] = '\0';


ども。

>前から疑問に思ってたんですけど、fgetsってなんで改行文字まで取り込んじゃうんでしょう?
>あれ邪魔でしょうがないです。
>getsにfがついてるだけなのにそういう細かいとこで動作が変わってるってのも統一性がなくてなんかヘン。
>なんか深い事情でもあったんでしょうか?

推測になりますが。。

バッファあふれが起きた場合、実際のデータを全部取り込むときのために
データの終端を知る必要があって、それを'\n'で示しているのだと思います。
つまり、

do {
 fgets();
 バッファに対する処理;
} while('\n'がくるまで);

みたいな使い方を想定しているのでは?
最近では現実に入力されるコマンドラインの長さよりも十分長いバッファ
(1kとかの)を用意するのはなんでもありませんが、Cが生まれた30年くらい
前ではそうでもなかったかもしれません(ので、何度も繰り返して取り込んで
処理する)。

あと、これも推測になりますが、他のfつきおよびfなしの入出力関数から
想像して、fgets()の特殊な形としてgets()が用意されたのかな、と
思います。標準入力からの取り込みはよく使うので、効率を重視した
(入力の長さを調べない)関数を用意しようということで。
# ただの手抜きの可能性もありますが

個人的な意見ではK&RのCからANSIのCになったときにgets()はなくしても
よかったのでは、と思います。実際は、それまでの資産があるということで
互換性のために残ったわけですが。
# 移植を担当する当事者ではないからこんなことがいえるのですが。。

>fgetsを使うときって下記みたいなコードでいいんでしょうか?
>いつも面倒でgets使っちゃうんで自信ないんです。
>
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf)-1] = '\0';


bufがポインタでないことが保証されているのであればあってると思います。
どっかでも書きましたが、テストとかならgets()でもいいかもしれません。
あと、なんども書くようであればマクロとかで逃げるのもありかも。
まあ、gets()には潜在的な危険があることを把握しつつ、場面に合わせて、
ということで。でも、コードを書く人はわかってても、読む人はわかってない
ということも考えられなくはないですが。

では。


どうも有り難う御座いました。

どこかの文章で、
「fgetsを使うとあらかじめ指定した大きさより長い文字列
があった場合には、指定した長さ(厳密には-1)まで、とり
あえず読み込んでくれるので、プログラムは安全に処理する
ことが出来ます。」と書いてありました。

bufのサイズを小さくしておいて、試してみたら、いろいろ
分かりました。fgetsでもサイズを超えた場合、その後のscanfなどに
影響が出たので、安全ではないですけど・・。


また質問する際には、宜しくお願いします。


ども。

>bufのサイズを小さくしておいて、試してみたら、いろいろ
>分かりました。fgetsでもサイズを超えた場合、その後のscanfなどに
>影響が出たので、安全ではないですけど・・。

これ、書いた後に、しまったーとおもいました。。
ほんとにほんとにちゃんとやるには前に書いたようにループ処理するか、
どうにかしてバッファをクリアしなければなりませんね。。。

なお、バッファをフラッシュする関数にfflush()というのがあります。
で、この関数を入力ストリーム(標準入力もこれに含まれます)に対して
使った場合、バッファがクリアされる処理系(LSI-C)があるようですが、
たしか、入力ストリームに使った場合の動作は未定義だったと思います。
規格や、使っている処理系のリファレンスを参照してみてください。

では。


返信ありがとうございいます。

>バッファあふれが起きた場合、実際のデータを全部取り込むときのために
>データの終端を知る必要があって、それを'\n'で示しているのだと思います。
ああ、なるほど。
なんかいっきに納得できてしまいました。
なんだかこれからはもっと好意的にfgetsを受け止められそうです。

> なんども書くようであればマクロとかで逃げるのもありかも。
そういえばその手がありました。
でもポインタを考慮するとなると難しいですね。
頭ひねってみます。

どうもありがとうございました。


こんにちは、ともじです。いつもサポートありがとうございます。
kikkさんの書き込み、いつもとても勉強になります。

>どっかでも書きましたが、テストとかならgets()でもいいかもしれません。
>あと、なんども書くようであればマクロとかで逃げるのもありかも。
>まあ、gets()には潜在的な危険があることを把握しつつ、場面に合わせて、
>ということで。でも、コードを書く人はわかってても、読む人はわかってない
>ということも考えられなくはないですが。

おしゃることは、よくわかります。
ただ初心者レベルですと、fgetsやfscanfはいきなりファイルIOの説明を
しなければならなくて、やっかいなので本編では使っていません。
ただ、認識しておくべき問題ですので、掲示板の過去ログをまとめるときに
本編からここへのリンクを貼っておきたいと思います。

ということで、今後もscanfもgetsも使いますが、ご了承ください。

戻る


「初心者のためのポイント学習C言語」 Last modified:2001.11.15
Copyright(c) 2000-2002 TOMOJI All Rights Reserved