20

snprintfバッファオーバーフローを回避できますが、なぜ関数が呼び出されないのですsnscanfか?

コード:

int main()
{
     char * src = "helloeveryone";
     char buf1[5];
     sscanf(src,"%s",buf1); // here is a  array out of bounds

}

snscanfなので、aも必要だと思います。なぜ私たちは持っているのsnprintfですか?

4

6 に答える 6

11

物議を醸している (およびオプションの) Annex K to C11 は、ポインター引数の後に (同じく Annex K で定義されている)sscanf_s型の追加の引数を取り、rsize_tポイント先の配列のサイズを指定する関数を追加します。良くも悪くも、これらの機能は広くサポートされていません。サイズを変換指定子に入れることで、同じ結果を得ることができます。

char out[20];
sscanf(in, "%19s", out);

しかし、宛先オブジェクトのサイズが実行時に変化する可能性がある場合、これは扱いにくく、エラーが発生しやすくなります ( を使用してプログラムで変換指定子を作成する必要がありますsnprintf)。変換指定子のフィールド幅は、読み取る入力文字の最大数であり、変換のためsscanfに終端の null バイトも書き込む%sため、渡すフィールド幅は宛先オブジェクトのサイズよりも厳密に小さくする必要があることに注意してください。

于 2013-08-21T22:49:07.513 に答える
10

snscanf()最初のバッファ引数への書き込みがないため、必要はありません。バッファの長さsnprintf()は、書き込み先のバッファのサイズを指定します。

char buffer[256];

snprintf(buffer, sizeof(buffer), "%s:%d", s, n);

に対応する位置のバッファはsscanf()ヌル終了文字列です。書き込むつもりはないので、明示的な長さは必要ありません ( const char * restrict bufferC99 と C11 では a です)。

char buffer[256];
char string[100];
int n;
if (sscanf(buffer, "%s %d", string, &n) != 2)
    ...oops...

出力では、文字列の長さを指定することがすでに期待されています (ただし、厳密に適切なものでは%sなく、または何でも使用する場合は、おそらく大多数です)。%99s

if (sscanf(buffer, "%99s %d", string, &n) != 2)
    ...oops...

と同じように使用できれば便利ですが%*ssnprintf()できません — では、長さではなく、「スキャンした値を割り当てない」という意味ですsscanf()。特に、1 回の呼び出しで複数の変換指定を使用できるため、 を*記述しないことに注意してください。特に、varargs リストを解析する際に解決できない問題が残るため、書き込みは意味がありません。書式文字列でフィールド サイズ (マイナス 1) を指定する必要をなくすような表記法があると便利です。残念ながら、今は et alでそれを行うことはできません。snscanf(src, sizeof(buf1), "%s", buf1)%ssnscanf(src, sizeof(buf1), sizeof(buf2), "%s %s", buf1, buf2)snscanf(src, "%@s %@s", sizeof(buf1), buf1, sizeof(buf2), buf2)sscanf()

ISO/IEC 9899:2011 の附属書 K (以前はTR24731 ) はsscanf_s()、文字列の長さを取り、次のように使用される可能性がある を提供します。

if (sscanf_s(buffer, "%s %d", string, sizeof(string), &n) != 2)
    ...oops...

(この理論上のオプションを思い出させてくれたR..に感謝します。理論的には、Microsoft だけが「安全な」機能を実装しており、標準が要求するとおりに実装していないためです。)

§K.3.3共通定義<stddef.h>には次のように記載されてrsize_tいることに注意してくださいsize_t385) ' (そして、脚注 385 は次のように述べています: ' の RSIZE_MAX マクロの説明を参照してください。これは、渡された値がin で定義された範囲内にある限り、実際にはキャストを必要とせずに<stdint.h>.渡すことができることを意味します。(一般的な意図は、これは大きいが より小さい数値です。詳細については、2011 規格を参照するか、オープン スタンダードのWeb サイトから TR 24731 を入手してください。)size_tRSIZE_MAX<stdint.h>RSIZE_MAXSIZE_MAX

于 2013-08-21T22:45:44.233 に答える
3

ではsscanf(s, format, ...)、スキャンされた文字の配列はconst char *. への書き込みはありませんss[i]が NULの場合、スキャンは停止します。nスキャンに対する補助的な制限としてのパラメーターはほとんど必要ありません。

ではsprintf(s, format, ...)、配列sは宛先です。 snprintf(s, n, format, ...)データが書き込まれないことを保証しますs[n]


便利なのは、コンパイル時に制限を簡単に指定できるように、sscanf() 変換指定子のフラグ拡張です。(現在は、動的フォーマットまたは を使用して、面倒な方法で行うことができますsscanf(src,"%4s",buf1)。)

// This is a proposed idea for C. - Not valid code today.
sscanf(src, "%!s", sizeof(buf1), buf)

ここでは、次の文字列のサイズ制限の変数を読み取るように!指示します。もしかしてC17?sscanf()size_t


今日でも機能する面倒な方法。

char * src = "helloeveryone";
char buf1[5];
char format[1+20+1+1];
sprintf(format, "%%" "%zu" "s", sizeof(buf1) - 1);
sscanf(src, format, buf1);
于 2013-08-21T22:43:30.080 に答える
1

fgets()(標準入力ファイルで)試してみませんstdinか?

fgets()バッファの最大サイズを指定できます。

(以下では、標準のISO C99互換の構文を使用します。)

したがって、次のコードを記述できます。

#include <stdio.h>
#define MAXBUFF 20 /* Small just for testing... */
int main(void) {
  char buffer[MAXBUFF+1]; /* Add 1 byte since fgets() inserts '\0' at end */
  fgets(buffer, MAXBUFF+1, stdin);
  printf("Your input was: %s\n", buffer);
  return 0;
}

fgets() は、標準入力(つまり、キーボード)stdinから最大 MAXBUFF 文字を読み取ります。 結果は配列に保持されます。 「\n」文字が見つかった場合、読み取りは停止し、「\n」も(最後の文字として) 保持されます。また、 の末尾には常に「\0」が追加されるため、十分なストレージが必要です。文字列を処理するために、次の 組み合わせを使用できます。

buffer
bufferbuffer
fgets()sscanf()

  char buffer[MAXBUFF+1];
  fgets(buffer, MAXBUFF+1, stdin); /* Plain read */
  int x; float f;
  sscanf(buffer, "%d %g", &x, &f); /* Specialized read */

したがって、「安全な」のscanf()ような方法があります。

注:このアプローチには潜在的な問題があります。fgets()行末文字 '\n' が取得される前に MAXBUFF 文字に達した場合、残りの入力は破棄されず、次のキーボード読み取りの一部として取り込まれます。
したがって、実際には非常に簡単な フラッシュメカニズムを追加する必要があります。

while(getchar()!'\n') 
    ; /* Flushing stdin... */

fgets()ただし、行の後に最後のコードを追加すると
、ユーザーはMAXBUFF 文字未満を入力するたびにENTERを 2 回押す必要があります。最悪: これは最も典型的な状況です!

この新しい問題を修正するには、文字 '\n' に到達しなかったという事実と完全に等価な簡単な論理条件が次のようになっていることに注意してください。

(buffer[MAXBUFF - 1] != '\0') && (buffer[MAXBUFF - 1] != '\n')

(証明する!)

したがって、次のように記述します。

fgets(buffer, maxb+1, stdin);
if ((buffer[MAXBUFF - 1] != '\0') && (buffer[MAXBUFF - 1] != '\n'))
     while(getchar() != '\n')
       ;

最後の仕上げが必要です。配列バッファにはガベージが含まれている可能性があるため、
何らかの初期化が必要なようです。
ただし、位置だけ[MAXBUFF - 1]をきれいにする必要があることに注意して ください。

char buffer[MAXBUFF + 1] = { [MAXBUFF - 1] = '\0' }; /* ISO C99 syntax */

最後に、このプログラムが示すように、すべての事実を簡単なマクロで収集できます。

#include <stdio.h>
#define safe_scanf(fmt, maxb, ...) { \
    char buffer[maxb+1] = { [maxb - 1] = '\0' }; \
    fgets(buffer, maxb+1, stdin); \
    if ((buffer[maxb - 1] != '\0') && (buffer[maxb - 1] != '\n')) \
        while(getchar() != '\n') \
           ; \
    sscanf(buffer, fmt, __VA_ARGS__); \
  }
#define MAXBUFF 20     

int main(void) {
  int x; float f;      
  safe_scanf("%d %g", MAXBUFF+1, &x, &f);
  printf("Your input was: x == %d\t\t f == %g",  x, f);
  return 0;
}

これは、 ISO C99基準 の下で、マクロ内の可変数のパラメーターのメカニズムとして使用されてきました。Variadic マクロは、パラメーターの変数リストを置き換えます。 ( のような動作を模倣するには、可変数のパラメーターが必要です。)

__VA_ARGS__
scanf()

注:マクロ本体は{ }でブロック内に囲まれていました。これは完全に満足できるものではなく、簡単に改善できますが、別のトピックの一部です...
特に、マクロsafe_scanf()は値を「返さない」(式ではなく、ブロック ステートメントです)。

注:マクロ内bufferで、ブロックに入るときに作成され、ブロックから出るときに破棄される配列を宣言しました。のスコープはbuffer、マクロのブロックに限定されます。

于 2013-08-22T03:58:54.387 に答える
1

もう少しシワ。「n」は通常、snprintf の最初の引数を指します。ここで、sscanf の最初の文字列引数が書き込まれないのは事実です。ただし読まれる。したがって、次の場合、セグメンテーション違反が発生する可能性があります。

char s[2];
s[0]='1'; s[1]='3';
int x;
sscanf(s, "%d", &x);

s を超えて 1 文字をステップ実行すると、不注意に未定義のメモリからの読み取りにステップインする可能性があるため (または、別の変数から整数を続行する可能性があります)。したがって、次のようなものが役立ちます。

 snscanf(s, 2, "%d", &x);

もちろん、s は文字列ではありませんが、文字配列です。snscanf の 'n' は、最初の (ソース文字列) 引数のオーバーステッピング (読み取り) を防ぎ、宛先引数とは関係ありません。

これを回避する方法は、最初に s が 2 文字以内の '\0' で終了していることを確認することです。もちろん、strlen は使用できません。strnlen と、それが 2 未満かどうかのテストが必要です。2 の場合は、最初にコピー作業がさらに必要になります。

于 2015-02-10T23:59:22.860 に答える
0

sscanf を正しく安全に使用する方法

fnprintf だけではなく、ほとんどの配列関数には安全なバリエーションがあることに注意してください。

于 2013-08-21T22:29:03.893 に答える