入力を読み取ったり解析したりしようとすると、Cプログラムに問題が発生します。
助けて?
これはFAQエントリです。
StackOverflowには、Cでの入力の読み取りに関連するmany質問があり、回答は通常、実際に全体像を描くことなく、その特定のユーザーの特定の問題に焦点を当てています。
これは多くの一般的な間違いを包括的にカバーする試みなので、この特定の一連の質問は、この問題の複製としてマークするだけで答えることができます。
scanf("%d", ...)
/scanf("%c", ...)
が失敗するのはなぜですか?gets()
がクラッシュするのはなぜですか?答えはコミュニティウィキとしてマークされています。自由に改善し、(慎重に)拡張してください。
「バイナリモード」ストリームは、書き込まれたとおりに読み込まれます。ただし、ストリームの末尾に、実装で定義された数のnull文字( '\0
')が追加される場合とされない場合があります。
「テキストモード」のストリームは、次のようないくつかの変換を実行できます(ただしこれらに限定されません)。
'\n'
)を別の何かに変更し(たとえば、Windowsでは"\r\n"
)、入力では'\n'
に戻します。isprint(c)
はtrue)、水平タブ、または改行の追加、変更、または削除。テキストモードとバイナリモードが混在しないことは明らかです。テキストファイルをテキストモードで開き、バイナリファイルをバイナリモードで開きます。
ファイルを開こうとする試みは、さまざまな理由で失敗する可能性があります-アクセス許可の欠如、またはファイルが見つからないことが最も一般的なものです。この場合、 fopen() はNULL
ポインターを返します。 常にfopen
がNULL
ポインタを返したかどうかを確認してから、ファイルの読み取りまたは書き込みを試みます。
fopen
が失敗すると、通常、グローバルな errno 変数を設定して、失敗した理由を示します。 (これは厳密にはC言語の要件ではありませんが、POSIXとWindowsの両方で保証されています。)errno
は、errno.h
の定数と比較できるコード番号ですが、単純なプログラムで使用できます、通常、必要なのは perror()
または strerror()
を使用してエラーメッセージに変換して出力することだけです。エラーメッセージには、fopen
に渡したファイル名も含める必要があります。これを行わないと、ファイル名が思ったとおりではないという問題があるときに、非常に混乱します。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: %s file\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
// alternatively, just `perror(argv[1])`
fprintf(stderr, "cannot open %s: %s\n", argv[1], strerror(errno));
return 1;
}
// read from fp here
fclose(fp);
return 0;
}
成功したかどうかを呼び出す関数をチェックします
これは明らかです。ただし、do呼び出した関数のドキュメントで戻り値とエラー処理を確認し、checkで条件を確認します。
これらは、早い段階で状態をキャッチすると簡単に発生するエラーですが、そうしないと、多くの頭を引っかくことになります。
EOF、または「なぜ最後の行が2回印刷されるのか」
関数 feof() は、EOFに達した場合にtrue
を返します。「到達する」ことについての誤解EOF =実際には、多くの初心者が次のようなものを書くことを意味します:
// BROKEN CODE
while (!feof(fp)) {
fgets(buffer, BUFFER_SIZE, fp);
printf("%s", buffer);
}
これにより、入力印刷の最後の行がtwiceになります。これは、最後の行が読み取られると(最後の改行まで、入力ストリームの最後の文字)、EOFは設定されていません設定されています
EOFは、最後の文字pastを読み取ろうとしたときにのみ設定されます。
したがって、上記のコードはもう一度ループします fgets() は別の行の読み取りに失敗し、EOFを設定してbuffer
untouched。その後、再び出力されます。
代わりに、fgets
が直接失敗したかどうかを確認します。
// GOOD CODE
while (fgets(buffer, BUFFER_SIZE, fp)) {
printf("%s", buffer);
}
gets() 、everは使用しないでください
この関数を安全に使用する方法はありません。 このため、C11の登場により言語から削除されました。
fflush() を使用しないでくださいstdin
またはその他の読み取り用に開いているストリーム、これまでにない
多くの人々は、まだ読まれていないユーザー入力をfflush(stdin)
が破棄することを期待しています。 それは行いません。プレーンISO Cでは、入力ストリームで fflush() を呼び出すと ndefined behaviour になります。 POSIXとMSVCでは明確に定義された動作がありますが、どちらもまだ読み取られていないユーザー入力を破棄しません。
通常、保留中の入力をクリアする正しい方法は、改行までの文字を読み取って破棄することです。
int c;
do c = getchar(); while (c != EOF && c != '\n');
不正な可能性のある入力には * scanf() を使用しないでください
多くのチュートリアルでは、あらゆる用途の入力を読み取るために * scanf() を使用することを教えています。
しかし、 * scanf() の目的は、事前に定義された形式であるときにある程度信頼できるバルクデータを読み取ることです。 (他のプログラムで書かれているなど)
それでも * scanf() は非監視者をつまずかせることができます:
[
、c
、およびn
変換)を除いて、入力内の先頭の空白はすべてスキップします。 (次の段落を参照してください。)* scanf() が期待どおりに機能しない場合
* scanf() でよくある問題は、ユーザーが考慮しなかった入力ストリームに未読の空白(' '
、'\n'
、...)がある場合です。
数値("%d"
など)または文字列("%s"
)の読み取りは、空白文字で停止します。そして、ほとんどの*scanf()
変換指定子skip入力の空白、[
、c
およびn
は、ない。したがって、改行はまだ最初の保留中の入力文字であり、%c
と%[
のいずれかが一致しなくなります。
明示的にそれを読むことで、入力の改行をスキップできます。 fgetc() を使用するか、空白を * scanf() フォーマット文字列に追加します。 (フォーマット文字列内の単一の空白は、入力内のany数の空白と一致します。)
* scanf() を使用しないことをお勧めします。ただし、実際に、積極的に、何をしているかを知っている場合は除きます。それで、代わりに何を使うのですか?
* scanf() が試行するように、入力を一度に読み取って解析する代わりに、ステップを分けます。
fgets()を介して入力行を(一部)読み取ります
fgets() には、バッファのオーバーフローを回避するために、入力を最大でそのバイト数に制限するためのパラメータがあります。入力行がバッファーに完全に収まった場合、バッファーの最後の文字が改行になります('\n'
)。すべてが収まらない場合は、部分的に読み取られた行を調べています。
メモリ内の行を解析します
strtol() および strtod() 関数ファミリは、メモリ内の解析に特に役立ちます。これらは、 * scanf() と同様の機能を提供します変換指定子d
、i
、u
、o
、x
、a
、e
、f
、およびg
。
しかし、それらはまた、解析を停止した場所に正確にを通知し、対象の型には大きすぎる数値の意味のある処理を行います。
それらを超えて、Cは 幅広い文字列処理関数 を提供します。入力はメモリにあり、いつまでにそれを解析したかを常に正確に知っているので、入力を理解するために何回でも戻ることができます。
そして、他のすべてが失敗した場合、ユーザーに役立つエラーメッセージを出力するために利用できる行全体があります。
(正常に)開いたストリームを明示的に閉じてください。これにより、まだ書き込まれていないバッファがフラッシュされ、リソースリークが回避されます。
fclose(fp);