77

のデメリットが知りたいですscanf()

多くのサイトで、使用scanfするとバッファ オーバーフローが発生する可能性があることを読みました。これの理由は何ですか?他に欠点はありscanfますか?

4

9 に答える 9

76

これまでの回答のほとんどは、文字列バッファ オーバーフローの問題に焦点を当てているようです。scanf実際には、関数で使用できる書式指定子は明示的なフィールド幅の設定をサポートしています。これにより、入力の最大サイズが制限され、バッファ オーバーフローが防止されます。scanfこれは、事実上根拠のない文字列バッファ オーバーフローの危険性の一般的な告発をレンダリングします。scanfその点で何らかの形で類似していると主張することgetsは完全に間違っています. と の間には質的な大きな違いがscanfありgetsます:scanfは文字列バッファのオーバーフローを防止する機能をユーザーに提供しますが、提供しgetsません。

フィールド幅をフォーマット文字列に埋め込む必要があるため、これらの機能は使いにくいと主張する人がいるscanfかもしれません (可変引数を介して渡す方法はありません。これは で行うことができますprintf)。それは実際に真実です。scanfその点で、実際にはかなり貧弱に設計されています。scanfしかし、それにもかかわらず、文字列バッファ オーバーフローの安全性に関してどうしようもなく破​​られているという主張は、完全に偽物であり、通常は怠惰なプログラマによって作成されます。

の本当の問題は、オーバーフローscanfにも関係していますが、まったく異なる性質を持っています。数値の 10 進数表現を算術型の値に変換するために関数を使用する場合、算術オーバーフローに対する保護は提供されません。オーバーフローが発生すると、未定義の動作が発生します。このため、C 標準ライブラリで変換を実行する唯一の適切な方法は、ファミリの関数です。scanfscanfstrto...

したがって、上記を要約するscanfと、文字列バッファを適切かつ安全に使用することが (可能ではありますが) 難しいという問題があります。また、算術入力に安全に使用することはできません。後者は本当の問題です。前者はただの不便です。

PS上記は、scanf関数のファミリー全体(fscanfおよびを含むsscanf)についてのものであることを意図しています。scanf特に、明らかな問題は、厳密にフォーマットされた関数を使用して潜在的にインタラクティブな入力を読み取るという考え自体がかなり疑わしいということです。

于 2010-03-12T06:45:37.687 に答える
70

scanf の問題点は次のとおりです (少なくとも):

  • ユーザーから文字列を取得するために使用%sすると、文字列がバッファよりも長くなり、オーバーフローが発生する可能性があります。
  • スキャンが失敗し、ファイル ポインターが不確定な場所に残る可能性があります。

fgets読み取るデータの量を制限できるように、行全体を読み取るために使用することを非常に好みます。1K バッファがあり、そこに行を読み込むと、fgets終了改行文字がないという事実によって行が長すぎるかどうかがわかります (ファイルの最後の行に改行がないにもかかわらず)。

次に、ユーザーに文句を言うか、行の残りの部分により多くのスペースを割り当てることができます (十分なスペースができるまで、必要に応じて継続的に割り当てます)。いずれの場合も、バッファ オーバーフローのリスクはありません。

行を読み込めば、次の行に位置していることがわかるので、問題はありません。sscanfその後、再読み取りのためにファイル ポインターを保存および復元することなく、心ゆくまで文字列を処理できます。

これは、ユーザーに情報を求めるときにバッファ オーバーフローが発生しないようにするために頻繁に使用するコードのスニペットです。

必要に応じて標準入力以外のファイルを使用するように簡単に調整できます。また、呼び出し元にそれを返す前に、独自のバッファーを割り当てて (十分に大きくなるまで増やし続ける) こともできます (ただし、呼び出し元が責任を負います)。もちろん、それを解放するためです)。

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

#define OK         0
#define NO_INPUT   1
#define TOO_LONG   2
#define SMALL_BUFF 3
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Size zero or one cannot store enough, so don't even
    // try - we need space for at least newline and terminator.

    if (sz < 2)
        return SMALL_BUFF;

    // Output prompt.

    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }

    // Get line with buffer overrun protection.

    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // Catch possibility of `\0` in the input stream.

    size_t len = strlen(buff);
    if (len < 1)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.

    if (buff[len - 1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[len - 1] = '\0';
    return OK;
}

そして、そのためのテストドライバー:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        // Extra NL since my system doesn't output that on EOF.
        printf ("\nNo input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long [%s]\n", buff);
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

最後に、実際の動作を示すためのテスト実行:

$ printf "\0" | ./tstprg     # Singular NUL in input stream.
Enter string>
No input

$ ./tstprg < /dev/null       # EOF in input stream.
Enter string>
No input

$ ./tstprg                   # A one-character string.
Enter string> a
OK [a]

$ ./tstprg                   # Longer string but still able to fit.
Enter string> hello
OK [hello]

$ ./tstprg                   # Too long for buffer.
Enter string> hello there
Input too long [hello the]

$ ./tstprg                   # Test limit of buffer.
Enter string> 123456789
OK [123456789]

$ ./tstprg                   # Test just over limit.
Enter string> 1234567890
Input too long [123456789]
于 2010-03-12T03:24:43.787 に答える
19

comp.lang.c FAQ から:なぜ誰もが scanf を使用しないと言うのですか? 代わりに何を使用すればよいですか?

scanfには多くの問題があります。質問12.1712.18a、および12.19を参照してください。また、その%s形式にも同じ問題がgets()あります (質問12.23を参照)。受信バッファーがオーバーフローしないことを保証するのは困難です。[脚注]

より一般的にscanfは、比較的構造化され、フォーマットされた入力用に設計されています (その名前は、実際には「スキャンフォーマット」に由来しています)。注意を払えば、成功したか失敗したかはわかりますが、どこで失敗したかはおおよそのことしかわかりません。どのように、またはなぜ失敗したかはわかりません。エラー回復を行う機会はほとんどありません。

しかし、インタラクティブなユーザー入力は、最も構造化されていない入力です。適切に設計されたユーザー インターフェイスでは、ユーザーは、文字や句読点だけでなく、数字が必要なときに文字や句読点を入力するだけでなく、予想よりも多くの文字や少ない文字を入力したり、文字をまったく入力したり (つまり、RETURN だけ) することもできます。キー)、または時期尚早の EOF、または何か。を使用する場合、これらの潜在的な問題すべてに適切に対処することはほぼ不可能scanfです。行全体を (などで) 読んでから、またはその他の手法fgetsを使用して解釈する方がはるかに簡単です。( 、、 などのsscanf関数はしばしば役立ちます。質問12.16および13.6も参照してください。)strtolstrtokatoiscanfバリアントの場合は、戻り値をチェックして、予期した数のアイテムが見つかったことを確認してください。また、 を使用する場合は%s、必ずバッファ オーバーフローを防止してください。

ところで、 に対する批判は、scanf必ずしも および の起訴とは限らないfscanfことに注意してくださいsscanfscanfから読み取りますstdin。これは通常、対話型キーボードであるため、制約が最も少なく、ほとんどの問題につながります。一方、データ ファイルの形式が既知の場合は、 で読み取るのが適切な場合がありますfscanfsscanf制御を取り戻したり、スキャンを再開したり、一致しなかった場合は入力を破棄したりするのはとても簡単なので、(戻り値がチェックされている限り)文字列を解析することは完全に適切です。

追加のリンク:

参考文献:K&R2 Sec. 7.4ページ 159

于 2010-03-12T06:39:41.100 に答える
6

scanfやりたいことをやり遂げるのはとても難しい。もちろんできますが、誰もが言っているように、 のようなものscanf("%s", buf);は と同じくらい危険です。gets(buf);

例として、読み取り関数で paxdiablo が行っていることは、次のようなもので実行できます。

scanf("%10[^\n]%*[^\n]", buf));
getchar();

上記は行を読み取り、改行以外の最初の 10 文字を に格納し、改行bufまで (および改行を含む) すべてを破棄します。scanfしたがって、paxdiablo の関数は次のように記述できます。

#include <stdio.h>

enum read_status {
    OK,
    NO_INPUT,
    TOO_LONG
};

static int get_line(const char *prompt, char *buf, size_t sz)
{
    char fmt[40];
    int i;
    int nscanned;

    printf("%s", prompt);
    fflush(stdout);

    sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
    /* read at most sz-1 characters on, discarding the rest */
    i = scanf(fmt, buf, &nscanned);
    if (i > 0) {
        getchar();
        if (nscanned >= sz) {
            return TOO_LONG;
        } else {
            return OK;
        }
    } else {
        return NO_INPUT;
    }
}

int main(void)
{
    char buf[10+1];
    int rc;

    while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
        if (rc == TOO_LONG) {
            printf("Input too long: ");
        }
        printf("->%s<-\n", buf);
    }
    return 0;
}

の他の問題の 1 つは、scanfオーバーフローの場合の動作です。たとえば、次を読み取る場合int:

int i;
scanf("%d", &i);

オーバーフローの場合、上記は安全に使用できません。fgets最初のケースでも、文字列の読み取りは を使用するよりもはるかに簡単scanfです。

于 2010-03-12T06:39:42.987 に答える
5

はい、あなたは正しいです。scanffamily( scanf, sscanf, ..etc) esp には、文字列を読み取るときに重大なセキュリティ上の欠陥があります。これは、(fscanf読み取り先の) バッファーの長さが考慮されていないためです。

例:

char buf[3];
sscanf("abcdef","%s",buf);

明らかに、バッファbufは最大3文字を保持できます。しかし、sscanfはそれに入れようとし"abcdef"、バッファ オーバーフローを引き起こします。

于 2010-03-12T03:25:55.313 に答える
4

ここでの多くの回答では、 を使用した場合の潜在的なオーバーフローの問題について説明していますが、最新の POSIX 仕様では、 、、およびformat の書式指定子で使用できる割り当て割り当て文字をscanf("%s", buf)提供することで、この問題を多かれ少なかれ解決しています。これにより、必要なだけのメモリを割り当てることができます(したがって、後で で解放する必要があります)。mcs[scanfmallocfree

その使用例:

char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.

// use buf

free(buf);

ここを参照してください。このアプローチの欠点は、これが POSIX 仕様に比較的最近追加されたものであり、C 仕様ではまったく指定されていないため、現時点では移植性が低いままであることです。

于 2014-10-03T01:20:04.813 に答える
4

*scanf()家族との問題:

  • %s および %[ 変換指定子でバッファ オーバーフローが発生する可能性があります。はい、フィールドの最大幅を指定できますが、 とは異なり、呼び出しprintf()で引数にすることはできません。scanf()変換指定子でハードコーディングする必要があります。
  • %d、%i などで算術オーバーフローが発生する可能性があります。
  • 不適切な形式の入力を検出して拒否する機能が制限されています。たとえば、「12w4」は有効な整数ではありませんが、scanf("%d", &value);12 を に正常に変換して代入しvalue、「w4」を入力ストリームに残して将来の読み取りを妨害します。理想的には、入力文字列全体を拒否する必要がありますが、それscanf()を行うための簡単なメカニズムはありません。

入力が常に固定長の文字列と数値で構成され、オーバーフローが発生しないことがわかっている場合scanf()は、優れたツールです。インタラクティブな入力や整形式であることが保証されていない入力を扱っている場合は、別のものを使用してください。

于 2010-03-12T15:18:24.457 に答える
3

scanf-like 関数には大きな問題が 1 つあります。それは、型安全性の欠如です。つまり、次のようにコーディングできます。

int i;
scanf("%10s", &i);

地獄、これでも「大丈夫」です:

scanf("%10s", i);

ポインタを期待するため、クラッシュが発生する可能性が高いため、printf-like 関数よりも悪いです。scanf

確かに、フォーマット指定子チェッカーはいくつかありますが、それらは完全ではなく、言語や標準ライブラリの一部でもありません。

于 2015-10-13T14:48:10.273 に答える