web-dev-qa-db-ja.com

文字配列のヌル終了

次の場合を検討してください。

_#include<stdio.h>
int main()
{
    char A[5];
    scanf("%s",A);
    printf("%s",A);
}
_

私の質問は、char _A[5]_に2文字しか含まれていないかどうかです。 「ab」、次に_A[0]='a'_、_A[1]='b'_、_A[2]='\0'_と言います。しかし、入力が「abcde」の場合、whereは_'\0'_です。 _A[5]_には_'\0'_が含まれますか?はいの場合、なぜですか? sizeof(A)は、答えとして常に5を返します。次に、配列がいっぱいになったときに、sizeof()がカウントしない_'\0'_用に予約された余分なバイトがありますか?

35
g4ur4v

4文字を超える文字を入力すると、余分な文字とヌルターミネータが配列の末尾の外側に書き込まれ、配列に属さないメモリが上書きされます。これはバッファオーバーフローです。

Cは、所有していないメモリを破壊することを防ぎません。その結果、未定義の動作になります。プログラムは何でもできます。クラッシュする可能性があり、他の変数を静かに破棄して混乱を招く動作を引き起こす可能性があります。プログラムが確実に動作するか、確実にクラッシュするという保証はありません。すぐにクラッシュすることに依存することさえできません。

これは、scanf("%s")が危険であり、決して使用すべきではない理由の良い例です。配列のサイズがわからないため、安全に使用する方法はありません。代わりに、scanfを避け、 fgets() のようなより安全なものを使用してください。

fgets()は、ストリームから最大でsize文字未満を読み取り、sが指すバッファーに格納します。 EOFまたは改行。改行が読み込まれると、バッファに保存されます。終端のヌルバイト( '\ 0')は、バッファの最後の文字の後に保存されます。 。

例:

_if (fgets(A, sizeof A, stdin) == NULL) {
    /* error reading input */
}
_

面倒なことに、fgets()は配列の最後に改行文字( '\ n')を残します。そのため、コードで削除することもできます。

_size_t length = strlen(A);
if (A[length - 1] == '\n') {
    A[length - 1] = '\0';
}
_

あー単純な(しかし壊れた)scanf("%s")は、7行の怪物に変わりました。そして、それはその日の2番目のレッスンです。CはI/Oと文字列の処理が苦手です。それは実行でき、安全に実行できますが、Cはずっと蹴って叫びます。

50
John Kugelman

既に指摘したように、N個の文字を正しく格納するには、長さN + 1の配列を定義/割り当てる必要があります。 scanfによって読み取られる文字の量を制限することが可能です。あなたの例では:

scanf("%4s", A);

最大を読むために。 stdinから4文字。

8
harpun

予約されている文字はありません。そのため、nullで終了できないポイントまで配列全体を埋めないように注意する必要があります。 char関数はnullターミネーターに依存しているため、説明した状況にいると、それらから悲惨な結果が得られます。

表示される多くのCコードは、strncpyなどの関数の「n」派生物を使用します。そのmanページから読むことができます:

Strcpy()およびstrncpy()関数はs1を返します。 stpcpy()およびstpncpy()関数は、s1の終了 `\ 0 '文字へのポインタを返します。 stpncpy()がs1をNUL文字で終了しない場合、代わりにs1 [n]へのポインタを返します(有効なメモリ位置を必ずしも参照するわけではありません)。

strlenは、文字バッファの長さを決定するためにヌル文字にも依存しています。そのキャラクターが見つからない場合、誤った結果が得られます。

3
RC.

最終的にndefined behaviourになります。

あなたが言うように、Aのサイズは常に5なので、5つ以上のcharsを読むと、scanfはメモリに書き込もうとしますが、それは想定されていません変更する。

いいえ、\0シンボル用に予約されたスペース/文字はありません。

3
Kiril Kirov

cの文字配列は、メモリブロックへの単なるポインタです。コンパイラーに文字用に5バイトを予約するように指示すると、コンパイラーはそうします。あなたがそこに5バイト以上を入れようとすると、予約した5バイトを過ぎてメモリが上書きされます。

それが、cが深刻なセキュリティ実装を行える理由です。 4文字+\0のみを書き込むことを知っている必要があります。 Cは、プログラムがクラッシュするまでメモリを上書きします。

Char foo [5]を文字列と考えないでください。 5バイトを置く場所と考えてください。 nullなしでそこに5文字を保存できますが、memcpy(otherCharArray、foo、5)を実行し、strcpyを使用しないでください。また、otherCharArrayにはこれらの5バイトに十分なスペースがあることを知っておく必要があります。

3
Doug

長さが4文字を超える文字列があると、scanfが配列の境界を超えて書き込みます。結果の動作は未定義であり、運がよければプログラムがクラッシュします。

なぜscanfが配列Aに格納するには長すぎる文字列の書き込みを停止しないのか疑問に思っているなら、それはscanfsizeof(A)は5です。C関数にパラメーターとして配列を渡すと、配列decaysは配列の最初の要素を指すポインターになります。そのため、関数内で配列のサイズを照会する方法はありません。

配列に読み込まれる文字の数を制限するには

scanf("%4s", A);
3
Praetorian