snprintf
バッファオーバーフローを回避できますが、なぜ関数が呼び出されないのですsnscanf
か?
コード:
int main()
{
char * src = "helloeveryone";
char buf1[5];
sscanf(src,"%s",buf1); // here is a array out of bounds
}
snscanf
なので、aも必要だと思います。なぜ私たちは持っているのsnprintf
ですか?
物議を醸している (およびオプションの) Annex K to C11 は、ポインター引数の後に (同じく Annex K で定義されている)sscanf_s
型の追加の引数を取り、rsize_t
ポイント先の配列のサイズを指定する関数を追加します。良くも悪くも、これらの機能は広くサポートされていません。サイズを変換指定子に入れることで、同じ結果を得ることができます。
char out[20];
sscanf(in, "%19s", out);
しかし、宛先オブジェクトのサイズが実行時に変化する可能性がある場合、これは扱いにくく、エラーが発生しやすくなります ( を使用してプログラムで変換指定子を作成する必要がありますsnprintf
)。変換指定子のフィールド幅は、読み取る入力文字の最大数であり、変換のためsscanf
に終端の null バイトも書き込む%s
ため、渡すフィールド幅は宛先オブジェクトのサイズよりも厳密に小さくする必要があることに注意してください。
snscanf()
最初のバッファ引数への書き込みがないため、必要はありません。バッファの長さsnprintf()
は、書き込み先のバッファのサイズを指定します。
char buffer[256];
snprintf(buffer, sizeof(buffer), "%s:%d", s, n);
に対応する位置のバッファはsscanf()
ヌル終了文字列です。書き込むつもりはないので、明示的な長さは必要ありません ( const char * restrict buffer
C99 と 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...
と同じように使用できれば便利ですが%*s
、snprintf()
できません — では、長さではなく、「スキャンした値を割り当てない」という意味ですsscanf()
。特に、1 回の呼び出しで複数の変換指定を使用できるため、 を*
記述しないことに注意してください。特に、varargs リストを解析する際に解決できない問題が残るため、書き込みは意味がありません。書式文字列でフィールド サイズ (マイナス 1) を指定する必要をなくすような表記法があると便利です。残念ながら、今は et alでそれを行うことはできません。snscanf(src, sizeof(buf1), "%s", buf1)
%s
snscanf(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_t
。385) ' (そして、脚注 385 は次のように述べています: ' の RSIZE_MAX マクロの説明を参照してください。これは、渡された値がin で定義された範囲内にある限り、実際にはキャストを必要とせずに<stdint.h>.
渡すことができることを意味します。(一般的な意図は、これは大きいが より小さい数値です。詳細については、2011 規格を参照するか、オープン スタンダードのWeb サイトから TR 24731 を入手してください。)size_t
RSIZE_MAX
<stdint.h>
RSIZE_MAX
SIZE_MAX
ではsscanf(s, format, ...)
、スキャンされた文字の配列はconst char *
. への書き込みはありませんs
。s[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);
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
buffer
buffer
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
、マクロのブロックに限定されます。
もう少しシワ。「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 の場合は、最初にコピー作業がさらに必要になります。
fnprintf だけではなく、ほとんどの配列関数には安全なバリエーションがあることに注意してください。