1

fgets次のような(たとえば、here )の使用法をいくつか見てきました。

char buff[7]="";

(...)

fgets(buff, sizeof(buff), stdin);

興味深いのは、「aaaaaaaaaaa」のような長い入力をfgets指定すると、7 番目の文字が'\0'.

ただし、これを行う場合:

int i=0;
for (i=0;i<7;i++)
{
    buff[i]='a';
}
printf("%s\n",buff);

私は常に 7'a'秒を取得し、プログラムはクラッシュしません。'a'しかし、8 sを書こうとすると、そうなるでしょう。

後で見たように、この理由は、少なくとも私のシステムでは、char buff[7]( の有無にかかわらず="") を割り当てると、8 番目のバイト (0 からではなく 1 から数えます) が 0 に設定されるためです。このように正確に行われるため、for7 回の書き込みとそれに続く文字列形式の読み取りを含むループは、最後に書き込まれる文字があったかどうかに関係なく成功'\0'し、プログラマーが最後の '\0 を設定する必要がなくなります。 ' 自分自身、文字を個別に書き込む場合。

このことから、

fgets(buff, sizeof(buff), stdin);

長すぎる入力を提供すると、結果のbuff文字列には自動的に 2 つの'\0'文字が含まれます。1 つは配列内にあり、もう 1 つはシステムによって書き込まれた直後です。

私はまた、そのことを観察しました

fgets(buff,(sizeof(buff)+17),stdin);

引き続き動作し、クラッシュすることなく非常に長い文字列を出力します。私が推測したところ、これは がfgetsまで書き込みを続けsizeof(buff)+17、最後に書き込まれる char が正確に a になり、'\0'今後の文字列読み取りプロセスが適切に終了することを保証するためです (ただし、メモリはめちゃくちゃになります)。

しかし、ではどうfgets(buff, (sizeof(buff)+1),stdin);ですか?これにより、 で正当に割り当てられたすべてのスペースが使い果たされbuff、その後に'\0'右側が書き込まれるため、'\0'以前にシステムによって書き込まれたスペースが上書きされます。言い換えれば、はい、fgets範囲外になりますが、書き込みの長さに 1 つだけ追加すると、プログラムがクラッシュすることは決してないことが証明できます。

最後に、ここで疑問が生じます:配列の直後にシステムによって配置された別のが既に存在fgetsしているのに、 は常に で書き込みを終了するのはなぜですか? 何も危険にさらすことなく、配列全体にアクセスし、プログラマーが必要とするものを何でも書き込むことができる、1 つずつループベースの書き込みのようにしてみませんか?'\0''\0'for

ご回答どうもありがとうございました!

'\0'編集: 確かに、 buff[7] の割り当て時に不思議なことに現れるこの 8 番目が、特に文字列配列の場合に C 標準の一部であるかどうかがわからない限り、可能な証拠はありません。そうでない場合は...それが機能するのはただの運です:-)

4

2 に答える 2

3

しかし、書き込みの長さに 1 つだけ追加しても、プログラムがクラッシュしないことは証明できます。

いいえ!あなたはそれを証明することはできません !数学的な証明という意味ではありません。システムで、コンパイラを使用し、使用した特定のコンパイラ設定を使用し、特定の環境構成を使用すると、クラッシュしない可能性があることを示しただけです。これは数学的な証明とはかけ離れています!

実際、C標準自体は、「配列の最後の要素の1つ後の場所」のアドレスを取得できることを保証していますが、そのアドレスを逆参照すること(つまり、そのアドレスから読み書きしようとすること)は未定義の動作であるとも述べています.

つまり、この場合、実装はすべてを行うことができます。単純な推論で期待どおりのことを実行することもできますが (つまり、仕事ですが、まったくの運です)、クラッシュしたり、HD をフォーマットしたりすることもあります (非常に不運な場合)。これは特に、システム ソフトウェア (デバイス ドライバーやベア メタルで実行されるプログラムなど) を作成する場合、つまり、悪いコードを作成することによる最悪の結果からユーザーを保護する OS がない場合に当てはまります。

編集これは、コメントで行われた質問に回答する必要があります(C99ドラフト標準):

7.19.7.2 fgets 関数

あらすじ

#include <stdio.h>
char *fgets(char * restrict s, int n,
    FILE * restrict stream);

説明

fgets 関数は、stream が指すストリームから s が指す配列に、n で指定された文字数より最大で 1 つ少ない文字を読み取ります。改行文字 (保持されます) の後またはファイルの終わりの後、追加の文字は読み取られません。ヌル文字は、配列に読み込まれた最後の文字の直後に書き込まれます。

戻り値

fgets 関数は、成功すると s を返します。ファイルの終わりが検出され、文字が配列に読み取られていない場合、配列の内容は変更されず、NULL ポインターが返されます。操作中に読み取りエラーが発生した場合、配列の内容は不確定であり、NULL ポインターが返されます。

編集:問題は文字列が何であるかの誤解にあるように思われるため、これは標準からの関連する抜粋です(私の強調):

7.1.1 用語の定義

文字列は、最初の null 文字で終了し、最初の null 文字を含む連続した文字列です。マルチバイト文字列という用語は、文字列に含まれるマルチバイト文字に与えられる特別な処理を強調したり、ワイド文字列との混同を避けるために代わりに使用されることがあります。文字列へのポインターは、最初の (アドレス指定が最も低い) 文字へのポインターです。文字列の長さはヌル文字ののバイト数であり、文字列の値は含まれている文字の値のシーケンスです。

于 2013-08-28T19:36:18.493 に答える
2

C11 標準ドラフトから:

fgets関数は 、stream が指すストリームから s が指す配列に、n で指定された文字数より最大で 1 つ少ない文字を読み取ります。改行文字 (保持されます) の後またはファイルの終わりの後に追加の文字は読み取られません。ヌル文字は、配列に読み込まれた最後の文字の直後に書き込まれます。

fgets 関数は、成功すると s を返します。ファイルの終わりが検出され、文字が配列に読み取られていない場合、配列の内容は変更されず、NULL ポインターが返されます。操作中に読み取りエラーが発生した場合、配列の内容は不確定であり、NULL ポインターが返されます。

あなたが説明する動作は未定義です。

于 2013-08-28T19:45:20.730 に答える