のデメリットが知りたいですscanf()
。
多くのサイトで、使用scanf
するとバッファ オーバーフローが発生する可能性があることを読みました。これの理由は何ですか?他に欠点はありscanf
ますか?
のデメリットが知りたいですscanf()
。
多くのサイトで、使用scanf
するとバッファ オーバーフローが発生する可能性があることを読みました。これの理由は何ですか?他に欠点はありscanf
ますか?
これまでの回答のほとんどは、文字列バッファ オーバーフローの問題に焦点を当てているようです。scanf
実際には、関数で使用できる書式指定子は明示的なフィールド幅の設定をサポートしています。これにより、入力の最大サイズが制限され、バッファ オーバーフローが防止されます。scanf
これは、事実上根拠のない文字列バッファ オーバーフローの危険性の一般的な告発をレンダリングします。scanf
その点で何らかの形で類似していると主張することgets
は完全に間違っています. と の間には質的な大きな違いがscanf
ありgets
ます:scanf
は文字列バッファのオーバーフローを防止する機能をユーザーに提供しますが、提供しgets
ません。
フィールド幅をフォーマット文字列に埋め込む必要があるため、これらの機能は使いにくいと主張する人がいるscanf
かもしれません (可変引数を介して渡す方法はありません。これは で行うことができますprintf
)。それは実際に真実です。scanf
その点で、実際にはかなり貧弱に設計されています。scanf
しかし、それにもかかわらず、文字列バッファ オーバーフローの安全性に関してどうしようもなく破られているという主張は、完全に偽物であり、通常は怠惰なプログラマによって作成されます。
の本当の問題は、オーバーフローscanf
にも関係していますが、まったく異なる性質を持っています。数値の 10 進数表現を算術型の値に変換するために関数を使用する場合、算術オーバーフローに対する保護は提供されません。オーバーフローが発生すると、未定義の動作が発生します。このため、C 標準ライブラリで変換を実行する唯一の適切な方法は、ファミリの関数です。scanf
scanf
strto...
したがって、上記を要約するscanf
と、文字列バッファを適切かつ安全に使用することが (可能ではありますが) 難しいという問題があります。また、算術入力に安全に使用することはできません。後者は本当の問題です。前者はただの不便です。
PS上記は、scanf
関数のファミリー全体(fscanf
およびを含むsscanf
)についてのものであることを意図しています。scanf
特に、明らかな問題は、厳密にフォーマットされた関数を使用して潜在的にインタラクティブな入力を読み取るという考え自体がかなり疑わしいということです。
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]
comp.lang.c FAQ から:なぜ誰もが scanf を使用しないと言うのですか? 代わりに何を使用すればよいですか?
scanf
には多くの問題があります。質問12.17、12.18a、および12.19を参照してください。また、その%s
形式にも同じ問題がgets()
あります (質問12.23を参照)。受信バッファーがオーバーフローしないことを保証するのは困難です。[脚注]より一般的に
scanf
は、比較的構造化され、フォーマットされた入力用に設計されています (その名前は、実際には「スキャンフォーマット」に由来しています)。注意を払えば、成功したか失敗したかはわかりますが、どこで失敗したかはおおよそのことしかわかりません。どのように、またはなぜ失敗したかはわかりません。エラー回復を行う機会はほとんどありません。しかし、インタラクティブなユーザー入力は、最も構造化されていない入力です。適切に設計されたユーザー インターフェイスでは、ユーザーは、文字や句読点だけでなく、数字が必要なときに文字や句読点を入力するだけでなく、予想よりも多くの文字や少ない文字を入力したり、文字をまったく入力したり (つまり、RETURN だけ) することもできます。キー)、または時期尚早の EOF、または何か。を使用する場合、これらの潜在的な問題すべてに適切に対処することはほぼ不可能
scanf
です。行全体を (などで) 読んでから、またはその他の手法fgets
を使用して解釈する方がはるかに簡単です。( 、、 などのsscanf
関数はしばしば役立ちます。質問12.16および13.6も参照してください。)strtol
strtok
atoi
scanf
バリアントの場合は、戻り値をチェックして、予期した数のアイテムが見つかったことを確認してください。また、 を使用する場合は%s
、必ずバッファ オーバーフローを防止してください。ところで、 に対する批判は、
scanf
必ずしも および の起訴とは限らないfscanf
ことに注意してくださいsscanf
。scanf
から読み取りますstdin
。これは通常、対話型キーボードであるため、制約が最も少なく、ほとんどの問題につながります。一方、データ ファイルの形式が既知の場合は、 で読み取るのが適切な場合がありますfscanf
。sscanf
制御を取り戻したり、スキャンを再開したり、一致しなかった場合は入力を破棄したりするのはとても簡単なので、(戻り値がチェックされている限り)文字列を解析することは完全に適切です。追加のリンク:
参考文献:K&R2 Sec. 7.4ページ 159
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
です。
はい、あなたは正しいです。scanf
family( scanf
, sscanf
, ..etc) esp には、文字列を読み取るときに重大なセキュリティ上の欠陥があります。これは、(fscanf
読み取り先の) バッファーの長さが考慮されていないためです。
例:
char buf[3];
sscanf("abcdef","%s",buf);
明らかに、バッファbuf
は最大3
文字を保持できます。しかし、sscanf
はそれに入れようとし"abcdef"
、バッファ オーバーフローを引き起こします。
ここでの多くの回答では、 を使用した場合の潜在的なオーバーフローの問題について説明していますが、最新の POSIX 仕様では、 、、およびformat の書式指定子で使用できる割り当て割り当て文字をscanf("%s", buf)
提供することで、この問題を多かれ少なかれ解決しています。これにより、必要なだけのメモリを割り当てることができます(したがって、後で で解放する必要があります)。m
c
s
[
scanf
malloc
free
その使用例:
char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.
// use buf
free(buf);
ここを参照してください。このアプローチの欠点は、これが POSIX 仕様に比較的最近追加されたものであり、C 仕様ではまったく指定されていないため、現時点では移植性が低いままであることです。
*scanf()
家族との問題:
printf()
で引数にすることはできません。scanf()
変換指定子でハードコーディングする必要があります。scanf("%d", &value);
12 を に正常に変換して代入しvalue
、「w4」を入力ストリームに残して将来の読み取りを妨害します。理想的には、入力文字列全体を拒否する必要がありますが、それscanf()
を行うための簡単なメカニズムはありません。 入力が常に固定長の文字列と数値で構成され、オーバーフローが発生しないことがわかっている場合scanf()
は、優れたツールです。インタラクティブな入力や整形式であることが保証されていない入力を扱っている場合は、別のものを使用してください。
scanf
-like 関数には大きな問題が 1 つあります。それは、型安全性の欠如です。つまり、次のようにコーディングできます。
int i;
scanf("%10s", &i);
地獄、これでも「大丈夫」です:
scanf("%10s", i);
ポインタを期待するため、クラッシュが発生する可能性が高いため、printf
-like 関数よりも悪いです。scanf
確かに、フォーマット指定子チェッカーはいくつかありますが、それらは完全ではなく、言語や標準ライブラリの一部でもありません。