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

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

ただいまの
回答率

91.36%

  • C

    2525questions

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

  • C++

    2412questions

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

  • GCC

    92questions

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

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

解決済

回答 2

投稿 2017/12/04 17:35

  • 評価
  • クリップ 0
  • VIEW 269

strike1217

score 480

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

テストプログラムです。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(){
    int num;
    char **ptr;

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

    ptr = (char **)calloc(num, sizeof(char *));

    if(ptr == NULL)
        puts("calloc error!!");
    else{
        int i;

        for(i = 0; i < num; i++){
            char temp[128];

            printf("ptr[%d] :", i);
            //if(fgets(temp, 128, stdin) == NULL)
            //    goto FREE;
            scanf("%127s%*[^\n]%*c", temp);

            ptr[i] = (char *)calloc(strlen(temp) + 1, sizeof(char));

            if(ptr[i] == NULL){
                puts("Error to calloc Second stage");
                goto FREE;
            } else {
                strncpy(ptr[i], temp, strlen(temp) + 1);
            }
        }

        putchar('\n');
        for(i = 0; i < num; i++)
            printf("ptr[%d] : %s\n", i, ptr[i]);

FREE:
        for(i = 0; i < num; i++)
            free(ptr[i]);
        free(ptr);
    }

    return 0;
}

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

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

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

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

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

fgets(temp, 128, stdin);


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

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

ptr[0] : 

ptr[1] : fhdklafuadsf

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

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

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

Linux 64bit gccです。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

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

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

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

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


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

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

char *myGets(char *buf, int size)
{
    char *ptr;

    ptr = fgets(buf, size, stdin);
    if (ptr == NULL)               // any error from fgets()
        return NULL;               // then, return error

    ptr = strchr(buf, '\n');       // search '\n' in input
    if (ptr != NULL)               // found '\n' !
        *ptr = '\0';               // erase '\n'

    return buf;
}


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

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


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

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

投稿 2017/12/05 01:33

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/05 02:07

    >即ち '\n' が無い場合もあります

    面倒くさくなって私のコードではstrlenで文字列末尾を得て後ろから2文字について改行文字か判別して置換するようにしてしまった。

    キャンセル

  • 2017/12/05 02:35

    >こんな関数を紹介しましょう。

    それむしろC11で追加されたgets_sでは

    キャンセル

  • 2017/12/05 11:01

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

    キャンセル

  • 2017/12/05 13:26

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

    ありがとうございます。

    キャンセル

  • 2017/12/05 13:31

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

    キャンセル

  • 2017/12/05 20:14

    C言語の標準入出力はラスボスだってもうn回言ってる

    C++でもstd::getlineとopoerator>>を混ぜて使うと似たような問題に直面します

    キャンセル

  • 2017/12/05 20:15

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

    キャンセル

+1

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

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


追記

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

--- 

再追記

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

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

投稿 2017/12/04 18:58

編集 2017/12/05 02:51

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/04 19:34

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

    キャンセル

  • 2017/12/04 19:50

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

    キャンセル

  • 2017/12/04 19:52

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

    キャンセル

  • 2017/12/04 19:54

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

    キャンセル

  • 2017/12/04 19:56

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

    キャンセル

  • 2017/12/04 20:30

    \0 \0

    キャンセル

  • 2017/12/04 20:57

    う〜〜ん。
    ダメみたいですね。
    うまく表示されないですね。

    scanf() -> fgets()に変更しただけでなんでこうなってしまうんですかね・・・

    キャンセル

  • 2017/12/04 20:58

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

    キャンセル

  • 2017/12/04 21:23

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

    キャンセル

  • 2017/12/04 22:15

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

    キャンセル

  • 2017/12/04 22:21

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

    キャンセル

  • 2017/12/04 23:11

    おお!!できました。
    すごい。

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

    キャンセル

  • 2017/12/05 00:02

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

    キャンセル

  • 2017/12/05 00:04

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

    キャンセル

  • 2017/12/05 00:14

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

    ああ、やはりそうですよね。
    なんとなくそうかもしれないと考えていました。

    ・・・・
    随分きれいなコード書きますね。かっこいい。
    失礼ですが、かなりできる方だったんですね。

    キャンセル

  • 2017/12/05 02:56

    真面目にエラー対策していくとどんどんネストが深くなってしかもどうやってもきれいにならないC言語やっぱり嫌だ。RAIIと例外を私にくれ、という思い。

    >随分きれいなコード書きますね。かっこいい。

    ループをfor文で書くだけできれいになりやすい印象。あとは変数を逐次宣言して先頭にまとめないこと。

    キャンセル

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

ただいまの回答率

91.36%

関連した質問

  • 解決済

    スタックの応用

    スタックを利用して入力された文字列の回文を作るプログラムを作成したら、出力されません。 例えば、「abcd」と入力したら、「abcddcba」と主著力される。 発生して

  • 解決済

    正しく動きません。。。

    include <stdio.h> int main(void) { int a=0,b=0; printf("入力をしてください\n"); while (1) {

  • 解決済

    Rubyから実行ファイルを実行して、対話的に処理を行いたい。

    タイトルのとおりです。 実行ファイルを、端末からではなくRubyで実行して、入出力を対話的に行いたいです。 ただ、下のプログラムを動かすと、なにも出力せずとまります。 メッセ

  • 解決済

    c言語 リスト構造の検索

    アドレス帳の検索機能だけのプログラムを作っています。 作りたいプログラムは、  1,検索したい人の名前を入力する  2,事前に登録された情報の中から部分一致検索する 

  • 解決済

    文字列のプログラムについて

    Cを使って3つの文字列を入力し一番文字数が最多の文字列を出力する(ただし最多の数と同じ数の文字列がある場合同じ数の文字列をすべて出力) というプログラムを作っているのですが今文字数

  • 解決済

    fgetsを使った文字列の分割

    前提・実現したいこと AOJ 1_5Aの問題で、よくないとされるscanf以外を使用した解決を図りたいです。 問題内容は、 トランプの枚数が足りないので現在持っているカードを入

  • 受付中

    【C言語】冗長だと思う数字入力プログラムを改善したい

    以下のプログラムは3つの数字をスペース区切りで入力して、入力した数字を改行区切りで出力するというコードです。 C言語はあまり慣れていないので、以下のコードに冗長さを感じますが何か改

  • 解決済

    [c]一つのデータでカラム数が変わるデータの読み込み

    質問失礼します。プログラム初心者です。 以下のような〜〜〜.datを fp1 = fopen(fname1,"r"); while((ret = fscanf(fp1,"%d %d

同じタグがついた質問を見る

  • C

    2525questions

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

  • C++

    2412questions

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

  • GCC

    92questions

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