13

次のコードを検討してください。

int main(void) {
    char * test = "abcdefghijklmnopqrstuvwxyz";
    テスト[5] = 'x';
    printf("%s\n", テスト);
    EXIT_SUCCESS を返します。
}

私の意見では、これは abcdexghij を出力するはずです。ただし、何も出力せずに終了します。

int main(void) {
    char * test = "abcdefghijklmnopqrstuvwxyz";
    printf("%s\n", テスト);
    EXIT_SUCCESS を返します。
}

ただし、これは問題なく機能するので、C文字列などを操作するという概念を誤解しましたか? 重要な点として、私は Mac OS X 10.6 を実行しており、コンパイル中の 32 ビット バイナリです。

4

5 に答える 5

32

初期化値で定義された Char ポインターは、読み取り専用セグメントに入ります。それらを変更可能にするには、ヒープ上に作成するか (new/malloc などを使用)、配列として定義する必要があります。

変更不可:

char * foo = "abc";

変更可能:

char foo[] = "abc";
于 2009-09-21T18:16:01.353 に答える
16

この答えは良いですが、完全ではありません。

char * test = "abcdefghijklmnopqrstuvwxyz";

文字列リテラルchar[N]は、静的記憶域期間 (プログラムの実行全体にわたって存在することを意味する) を持つ型の無名配列オブジェクトを参照します。ここNで、 は文字列の長さに終端の'\0'. このオブジェクトは ではありませんconstが、変更しようとすると未定義の動作が発生します。(実装、必要に応じて文字列リテラルを書き込み可能にすることができますが、最新のコンパイラのほとんどはそうではありません。)

上記の宣言は、タイプ の無名オブジェクトを作成char[27]し、そのオブジェクトの最初の要素のアドレスを使用して を初期化しますtest。したがって、代入のようなtest[5] = 'x'配列を変更しようとすると、動作が未定義になります。通常、プログラムがクラッシュします。(リテラルは配列型の式であり、ほとんどのコンテキストで配列の最初の要素へのポインターに暗黙的に変換されるため、初期化ではアドレスが使用されます。)

C++ では、文字列リテラルは実際にconstは であり、上記の宣言は無効になることに注意してください。C または C++ では、consttestへのポインターとして宣言するのが最善です。 char

const char *test = "abcdefghijklmnopqrstuvwxyz";

そのため、 を介して配列を変更しようとすると、コンパイラから警告が表示されますtest

(C 文字列リテラルはconst歴史的な理由によるものではありません。1989 年の ANSI C 標準より前には、constキーワードは存在しませんでした。あなたのような宣言で使用することを要求すると、より安全なコードが作成されますが、既存のコードを変更する必要がありました。 ANSI 委員会が回避しようとしたこと.文字列リテラルはそうでconstなくても. .)-Wwrite-stringsconst

参照する文字列を変更できるようにしたい場合はtest、次のように定義できます。

char test[] = "abcdefghijklmnopqrstuvwxyz";

コンパイラはイニシャライザを調べて、test必要なサイズを決定します。この場合、testタイプは になりますchar[27]。文字列リテラルは引き続き匿名のほとんど読み取り専用の配列オブジェクトを参照しますが、その値は にコピーさtestます。(配列オブジェクトを初期化するために使用される初期化子の文字列リテラルは、配列がポインターに「減衰」しないコンテキストの 1 つです。その他は、それが単項&またはのオペランドである場合sizeofです。)無名配列の場合、コンパイラはそれを最適化して削除する場合があります。

この場合、それ自体は、指定した 26 文字とターミネータtestを含む配列です。'\0'その配列の有効期間は、test宣言されている場所によって異なりますが、これは問題になる場合とそうでない場合があります。たとえば、次のようにします。

char *func(void) {
    char test[] = "abcdefghijklmnopqrstuvwxyz";
    return test; /* BAD IDEA */
}

呼び出し元は、もはや存在しないものへのポインターを受け取ります。が定義されているスコープ外の配列を参照する必要がある場合はtest、 として定義するか、staticを使用して割り当てることができますmalloc

char *test = malloc(27);
if (test == NULL) {
    /* error handling */
}
strcpy(test, "abcdefghijklmnopqrstuvwxyz";

そのため、配列は を呼び出すまで存在し続けますfree()。非標準strdup()関数がこれを行います (これは POSIX で定義されていますが、ISO C では定義されていません)。

test宣言方法に応じて、ポインターまたは配列のいずれかになる可能性があることに注意してください。文字列関数に渡す場合test、または を取る関数に渡す場合char*、それは問題ではありませんが、 がポインターか配列sizeof testかによって動作が大きく異なります。test

comp.lang.c FAQは優れています。セクション 8 では文字と文字列について説明し、質問 8.5 は質問 1.32 を示しています。これはあなたの特定の質問に対応しています。セクション 6 では、配列とポインターの間の混乱しがちな関係について説明します。

于 2013-09-28T20:50:45.313 に答える
4

文字列リテラルは変更できない場合があります。そうではないと想定するのが最善です。詳細については、こちらをご覧ください。

于 2009-09-21T18:38:46.150 に答える
4

変数の型を初期化子の型と一致させる習慣を身につける必要があります。この場合:

const char* test = "abcdefghijklmnopqrstuvwxyz";

そうすれば、実行時エラーではなくコンパイラ エラーが発生します。コンパイラの警告レベルを最大まで上げると、このような落とし穴を回避するのにも役立ちます。これが C のエラーではない理由は、おそらく歴史的なものです。初期のコンパイラはそれを許可し、禁止すると、言語が標準化されたときに既存のコードを壊しすぎた可能性があります. ただし、現在はオペレーティングシステムで許可されていないため、アカデミックです。

于 2009-09-21T18:29:33.017 に答える
0

行う:

 char * bar = strdup(foo);
 bar[5] = 'x';

strdup変更可能なコピーを作成します。

strdupはい、 NULL を返さなかったことを実際にテストする必要があります。

于 2010-02-07T01:13:00.380 に答える