文字配列を文字列として使用するにはどうすればよいですか?


10

Cの文字列は単なる文字配列であることを理解しています。そこで、次のコードを試しましたが、ガベージ出力やプログラムのクラッシュなど、奇妙な結果が得られました。

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

なぜこれが機能しないのですか?

できれいにコンパイルされますgcc -std=c17 -pedantic-errors -Wall -Wextra


注:この投稿は、文字列を宣言するときにNULターミネーター用のスペースを割り当てられなかったことから生じる問題の標準的なFAQとして使用することを目的としています。

回答:


12

C文字列は、nullターミネータで終わる文字配列です。

すべての文字にシンボルテーブル値があります。nullターミネータは、シンボル値0(ゼロ)です。文字列の終わりを示すために使用されます。文字列のサイズはどこにも保存されないため、これは必要です。

したがって、文字列用のスペースを割り当てるたびに、ヌルターミネータ文字用に十分なスペースを含める必要があります。あなたの例ではこれを行わず、の5文字分のスペースのみを割り当てます"hello"。正しいコードは次のとおりです。

char str[6] = "hello";

または同等に、5文字と1つのヌルターミネーター用の自己文書化コードを記述できます。

char str[5+1] = "hello";

文字列のメモリを実行時に動的に割り当てる場合は、nullターミネーター用のスペースも割り当てる必要があります。

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

文字列の最後にnullターミネータを追加しない場合、文字列を予期するライブラリ関数は正しく機能せず、ガベージ出力やプログラムクラッシュなどの「未定義の動作」のバグが発生します。

Cでnullターミネーター文字を書き込む最も一般的な方法は、次のように、いわゆる「8進エスケープシーケンス」を使用することです'\0'。これはを書くことと100%同等です0が、\はゼロが明示的にnullターミネーターであることを明示する自己文書化コードとして機能します。などのコードif(str[i] == '\0')は、特定の文字がnullターミネータかどうかをチェックします。

nullターミネーターという用語は、nullポインターやNULLマクロとは関係がないことに注意してください。これは紛らわしい場合があります-非常に似た名前ですが、非常に異なる意味です。これが、ヌルターミネーターがNUL1つのL と呼ばれることもあり、NULLまたはヌルポインターと混同しないようにする理由です。詳細については、このSOの質問に対する回答を参照してください。

"hello"あなたのコードでは、と呼ばれている文字列リテラル。これは読み取り専用の文字列と見なされます。""コンパイラはリテラル自動的に文字列の最後にヌル・ターミネータを追加することを構文手段。したがってsizeof("hello")、nullターミネーターを含む配列のサイズを取得するため、出力すると5ではなく6が得られます。


それはgccできれいにコンパイルします

実際、警告すらありません。これは、C言語の微妙な詳細/欠陥が原因で、文字配列を、配列内のスペースと同じ数の文字を含む文字列リテラルで初期化し、nullターミネーターを静かに破棄するためです(C17 6.7.9 / 15)。歴史的な理由により、この言語は意図的にこのように動作しています。詳細については、文字列初期化の一貫性のないgcc診断を参照してください。また、C ++はここでは異なり、このトリック/欠陥を使用できないことに注意してください。


1
あなたはchar str[] = "hello";事件に言及すべきです。
Jabberwocky

@JabberwockyこれはコミュニティWikiです。自由に編集して投稿してください。
ランディン

1
...そして多分char *str = "hello";... str[0] = foo;問題も。
Jabberwocky

おそらくsizeof、特に配列として定義されている場合は、使用の影響を関数パラメーターでの使用に拡張します。
Weather Vane

:@WeatherVaneは、ここに別の質問でカバーされるべきであるstackoverflow.com/questions/492384/...
ランディン

4

C標準から(7.1.1用語の定義)

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

この宣言では

char str [5] = "hello";

文字列リテラル"hello"は次のような内部表現を持っています

{ 'h', 'e', 'l', 'l', 'o', '\0' }

したがって、終了ゼロを含めて6文字です。その要素を使用してstr、5文字だけのスペースを予約する文字配列を初期化します。

C標準(C ++標準の反対)では、文字列リテラルの終了ゼロが初期化子として使用されていない場合に、このような文字配列の初期化が可能です。

ただし、その結果、文字配列にstrは文字列が含まれません。

配列に文字列を含める場合は、次のように記述できます

char str [6] = "hello";

あるいは単に

char str [] = "hello";

最後のケースでは、文字配列のサイズは、6に等しい文字列リテラルの初期化子の数から決定されます。


0

すべての文字列を文字配列と見なすことができますか(はい)、すべての文字配列を文字列と見なすことができますいいえ)。

何故なの?そして、なぜそれが重要なのですか?

文字列の長さが文字列の一部としてどこにも保存されないことを説明する他の回答と、文字列が定義されている標準への参照に加えて、裏側は「Cライブラリ関数は文字列をどのように処理するか」です。

文字配列は同じ文字を保持できますが、最後の文字の後にヌル終了文字が続いていない限り、それは単に文字の配列です。そのNUL終了文字は、文字の配列を文字列と見なす(扱う)ことを可能にする文字です。

文字列を引数として期待するCのすべての関数は、文字シーケンスがヌル文字で終了することを期待していますどうして?

すべての文字列関数が機能する方法に関係しています。長さは配列、string-functionsの一部として含まれていないため、nul文字(たとえば、'\0'、decimalに相当0)が見つかるます。ASCIIテーブルと説明を参照してください。かかわらず、あなたが使用しているかどうかstrcpystrchrstrcspn、など。すべての文字列関数は、に依存しているNUL終端その文字列の終わりがどこにあるかを定義するために存在している文字。

からの2つの類似した関数の比較はstring.hNUL終了文字の重要性を強調します。例えば:

    char *strcpy(char *dest, const char *src);

strcpy単にコピー機能からバイトsrcdestは、nullで終了する文字が見つかりstrcpy、文字のコピーを停止する場所を指示まで、コピーします。次に、同様の関数を実行しますmemcpy

    void *memcpy(void *dest, const void *src, size_t n);

関数は同様の操作を実行しsrcますが、パラメーターを文字列と見なしたり、パラメーターを必要としたりしません。までバイトをコピーするmemcpyだけでは順方向にスキャンできないので、srcdest nul終了文字に到達、3番目のパラメーターとしてコピーするバイト数を明示的に要求します。この3番目のパラメーターは、null終了文字が見つかるまで前方にスキャンするだけでmemcpy同じサイズの情報strcpyを取得できます。

(これはstrcpy、関数に文字列を提供するのに失敗した場合、何が悪いのか(または文字列を期待する関数)を強調します) nullで終了する文字列をます-どこに停止ず、残りのメモリセグメント全体でうまく競合します。メモリ内のどこかでヌル文字が偶然見つかるまで、またはセグメンテーション違反が発生するまで、未定義の動作を呼び出す)

つまり、なぜ期待機能NUL終端文字列が渡されなければなりませんNUL終端文字列をし、なぜそれが重要


0

直感的に...

配列を変数(物を保持する)と見なし、文字列を値(変数に配置できる)と見なします。

彼らは確かに同じものではありません。あなたの場合、変数が小さすぎて文字列を保持できないため、文字列が途切れます。(Cの「引用符付き文字列」は、末尾に暗黙のnull文字があります。)

ただし、文字列よりはるかに大きい配列に文字列を格納することは可能です。

通常の代入演算子や比較演算子(= == <など)は、期待どおりに機能しないことに注意してください。しかし、strxyzあなたが何をしているのかわかったら、関数のファミリーはかなり接近します。文字列配列に関するC FAQを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.