web-dev-qa-db-ja.com

C標準は、バッファがヌルターミネータを超えて触れられないことを保証しますか?

標準ライブラリの多くの文字列関数にバッファが提供されるさまざまなケースで、バッファがnullターミネータを超えて変更されないことが保証されていますか?例えば:

char buffer[17] = "abcdefghijklmnop";
sscanf("123", "%16s", buffer);

buffer"123\0efghijklmnop"と等しくなる必要がありますか?

もう一つの例:

char buffer[10];
fgets(buffer, 10, fp);

読み取り行の長さが3文字しかない場合、6番目の文字がfgetsが呼び出される前と同じであると確信できますか?

43
Segmented

バッファ内の個々のバイトはオブジェクトです。 sscanfまたはfgetsの関数の説明の一部で、これらのバイトの変更について言及されていない限り、またはそれらの値が変更される可能性があることを示唆している場合を除きます。それらの値が不特定になると述べることにより、一般的な規則が適用されます:(私の強調)

6.2.4オブジェクトの保存期間

2 [...]オブジェクトが存在し、定数アドレスを持ち、その存続期間を通じて最後に保存された値を保持します 。 [...]

それを保証するのはこれと同じ原則です

#include <stdio.h>
int a = 1;
int main() {
  printf ("%d\n", a);
  printf ("%d\n", a);
}

1を2回印刷しようとします。 aはグローバルですが、printfはグローバル変数にアクセスでき、printfの説明にはnotaを変更します。

fgetsの説明もsscanfの説明も、実際に書き込まれるはずのバイトを超えてバッファを変更することについては言及していないため(読み取りエラーの場合を除く)、それらのバイトは取得されません。変更されました。

24
user743382

C99ドラフト 標準は、これらの場合に何が起こるかを明示的に述べていませんが、複数のバリエーションを検討することにより、すべての場合に仕様を満たすために特定の方法で機能する必要があることを示すことができます。

標準は言う:

%s-空白以外の文字のシーケンスに一致します。252)

L長さ修飾子が存在しない場合、対応する引数は、シーケンスを受け入れるのに十分な大きさの文字配列の最初の要素へのポインターと、自動的に追加される終了ヌル文字でなければなりません。

これは、基準を満たすために提案している方法で機能する必要があることを示す2つの例です。

例A:

char buffer[4] = "abcd";
char buffer2[10];  // Note the this could be placed at what would be buffer+4
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer =  "123\0"
//           buffer2 = "4\0"

例B:

char buffer[17] = "abcdefghijklmnop";
char* buffer2 = &buffer[4];
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer = "123\04\0"

Sscanfのインターフェースは、これらが異なっていることを実際に知るのに十分な情報を提供しないことに注意してください。したがって、例Bが正しく機能する場合は、例Aのヌル文字の後のバイトを混乱させてはなりません。これは、この仕様のビットに従って両方の場合に機能する必要があるためです。

したがって、暗黙的に仕様により、指定したとおりに機能する必要があります。

他の関数についても同様の議論をすることができますが、この例からアイデアを見ることができると思います。

注:「%16s」などの形式でサイズ制限を指定すると、は動作を変更できます。仕様により、sscanfがデータをバッファに書き込む前に、バッファを限界までゼロにすることは機能的に許容されます。実際には、ほとんどの実装はパフォーマンスを選択します。つまり、残りはそのままにしておきます。

仕様の目的がこの種のゼロ化を行うことである場合、通常は明示的に指定されます。 strncpyはその一例です。文字列の長さが指定された最大バッファ長より短い場合、残りのスペースはヌル文字で埋められます。この同じ「文字列」関数が終了していない文字列も返す可能性があるという事実により、これは人々が独自のバージョンをロールするための最も一般的な関数の1つになります。

Fgetsに関しては、同様の状況が発生する可能性があります。唯一の落とし穴は、何も読み込まれない場合、バッファは変更されないままであると仕様が明示的に述べていることです。許容可能な機能実装は、バッファをゼロにする前に、読み取るバイトが少なくとも1つあるかどうかを確認することで、これを回避できます。

31
caveman

標準はこれに関していくぶん曖昧ですが、それの合理的な読み方は答えが次のとおりであると思います:はい、それがread + nullより多くのバイトをバッファに書き込むことは許可されていません。一方、テキストをより厳密に読んだり解釈したりすると、答えはノーであり、保証はないと結論付けることができます。 公開されているドラフトfgetsについて言っていることは次のとおりです。

char *fgets(char * restrict s, int n, FILE * restrict stream);

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

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

入力から読み取りすることになっている量についての保証があります。つまり、改行またはEOFで読み取りを停止し、詳細を読み取らないようにします。 n-1バイトより。バッファへの書き込みが許可される量については明確に述べられていませんが、一般的な知識はfgetsnパラメータは、バッファオーバーフローを防ぐために使用されます。標準があいまいな用語readを使用しているのは少し奇妙ですが、これは必ずしもgetsが使用できないことを意味するわけではありませんnバイトを超えるバッファへの書き込み。使用する用語を厳選したい場合。ただし、両方の問題について同じ「読み取り」用語が使用されていることに注意してください。n- limitとEOF/newline制限です。したがって、n関連の「読み取り」をバッファ書き込み制限として解釈する場合、[一貫性を保つために]他の「読み取り」も同じように解釈できます。つまり、読み取った内容を超えて書き込むことはできません。文字列がバッファより短い場合。

一方、句動詞「read into」(= "write")と単に「read」の使用法を区別すると、委員会のテキストを同じように読むことはできません。配列がnバイトを超えて「読み込まれる」(= "書き込みされる")ことはありませんが、入力文字列が改行またはEOFによって早く終了する場合は'(入力の)残りが「読み取られる」ことは保証されませんが、それが「読み込まれる」(= "書き込まれる")ことを意味するかどうかは、この厳密な読み取りでは不明確です。重要な問題は、キーワードが「into」であり、これが省略されていることです。したがって、問題は、次の変更された引用で括弧内に示されている補完が意図された解釈であるかどうかです。

改行文字(保持される)の後、またはファイルの終わりの後、追加の文字は[配列に]読み込まれません。

率直に言って、式として記述された単一の 事後条件 (そしてこの場合はかなり短いでしょう)は、私が引用した言葉よりもはるかに役に立ちました...

*scanfファミリーに関する彼らの記事を分析しようとするのは面倒ではありません。これらの関数で発生する他のすべてのことを考えると、さらに複雑になると思われるからです。 fscanfの記述は約5ページの長さです...しかし、同様の論理が当てはまると思います。

8
Fizz

バッファがヌルターミネータを超えて変更されないことが保証されていますか?

いいえ、保証はありません。

バッファは「123\0efghijklmnop」と等しくなる必要がありますか?

はい。しかし、それは、文字列関連の関数に正しいパラメーターを使用したからです。バッファの長さを台無しにしたり、修飾子をsscanfに入力したりすると、プログラムがコンパイルされます。ただし、実行時に失敗する可能性があります。

読み取り行の長さが3文字しかない場合、6番目の文字がfgetsが呼び出される前と同じであると確信できますか?

はい。 fgets()が3文字の入力文字列を取得すると、入力は提供されたバッファに格納され、提供されたスペースのリセットはまったく気になりません。

4
Igor S.K.

バッファは「123\0efghijklmnop」と等しくなる必要がありますか?

ここで、bufferは、NULで終了することが保証されている123文字列で構成されています。

はい、配列bufferに割り当てられたメモリは割り当て解除されませんが、文字列bufferには、読み取り可能な16文字要素しか含めることができないことを確認/制限しています。いつでもそれ。ここで、1文字だけを書き込むか、bufferが取ることができる最大値を書き込むかによって異なります。

例えば:

char buffer[4096] = "abc";` 

実際に以下のことをします、

memcpy(buffer, "abc", sizeof("abc"));
memset(&buffer[sizeof("abc")], 0, sizeof(buffer)-sizeof("abc"));

標準では、char配列のいずれかの部分が初期化された場合、そのすべてがメモリ境界に従うまでいつでも構成されていると主張しています。

1

標準からの保証はありません。そのため、質問で示すように(およびを使用して)関数sscanfおよびfgetsを(バッファーのサイズに関して)使用することをお勧めします。 fgetsは、getsと比較して好ましいと見なされます。

ただし、一部の標準関数は、作業でnullターミネータを使用します。 strlen(ただし、文字列の変更について質問すると思います)

編集:

あなたの例では

fgets(buffer, 10, fp);

10日以降は文字に触れないことが保証されます(bufferの内容と長さはfgetsによって考慮されません)

EDIT2:

さらに、fgetsを使用するときは、'\n'はバッファに保存されます。例えば.

 "123\n\0fghijklmnop"

期待される代わりに

 "123\0efghijklmnop"
0
VolAnd

使用中の関数(および程度は低いですがその実装)によって異なります。 sscanfは、最初の非空白文字に遭遇すると書き込みを開始し、最初の空白文字まで書き込みを続けます。最初の空白文字で、最後の0が追加されて戻ります。しかし、strncpy(有名な)のような関数は、残りのバッファーをゼロにします。

ただし、C標準には、これらの関数の動作を義務付けるものはありません。

0
Steve D