2009-09-21 8 views
7

次のコードを検討してください。Cの文字列を変更できません

 
int main(void) { 
    char * test = "abcdefghijklmnopqrstuvwxyz"; 
    test[5] = 'x'; 
    printf("%s\n", test); 
    return EXIT_SUCCESS; 
} 

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

 
int main(void) { 
    char * test = "abcdefghijklmnopqrstuvwxyz"; 
    printf("%s\n", test); 
    return EXIT_SUCCESS; 
} 

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

+1

私はそれを言いたくはありませんが、これは実際にCのFAQのどこかにあるはずです...スタックオーバーフローで何十何千もの他の時間が既に要求されています。 – ephemient

+0

これまでに聞いたことがあれば申し訳ありませんが、答えが見つかりませんでした。私は実際に関数のリファレンスとすべてを最初に読んだのですが、私が間違っていたことは実際には分かりませんでした。このようなC FAQに私を指摘できますか? – fresskoma

+3

@ x3ro:誰も4年間でC FAQについてあなたに答えましたか? [comp.lang.c FAQ](http://www.c-faq.com/)は優れています。セクション8は文字と文字列をカバーし、質問8.5は質問1.32を指し、特定の質問に対処します。 –

答えて

4

accepted answerは良好ですが、完全ではありません。

char * test = "abcdefghijklmnopqrstuvwxyz"; 

Nは、文字列の長さを加えたものである、(それはプログラムの全体の実行のために存在することを意味する)静的記憶寿命タイプchar[N]の匿名配列オブジェクトを指す文字列リテラル終了する'\0'。このオブジェクトはconstではありませんが、変更しようとすると未定義の動作が発生します。 (実装、それが選択した場合文字列リテラルを書き込み可能にすることができますが、最も近代的なコンパイラはしないでください。)

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

C++では、文字列リテラルは実際にはconstであり、上記の宣言は無効であることに注意してください。あなたはtest経由で配列を変更しようとするので、コンパイラは警告を表示します

const char *test = "abcdefghijklmnopqrstuvwxyz"; 

:CまたはC++のいずれかで、それはのconstcharへのポインタとしてtestを宣言するのが最善です。

(歴史的な理由から、Cの文字列リテラルはconstではありません)1989 ANSI C規格の前には、constキーワードは存在しませんでした。あなたのような宣言で使用することを要求すると、既存のコードを修正する必要がありますが、ANSI委員会が避けようとしたことです。のように、文字列リテラルはconstでなければなりません。gccを使用している場合、-Wwrite-stringsオプションは、リテラルはconst - gccが非適合になります)

の文字列を変更したい場合はコンパイラはtestをする必要がありますどのように大きな決定するために初期化子を見

char test[] = "abcdefghijklmnopqrstuvwxyz"; 

:は、あなたがこのようにそれを定義することができ、を指します。この場合、testchar[27]となります。文字列リテラルは、依然として匿名の主に読み取り専用の配列オブジェクトを参照しますが、その値はで、testがコピーされています。 (配列オブジェクトを初期化するために使用される初期化子の文字列リテラルは、配列がポインタに対して "崩壊"しないコンテキストの1つであり、他のものは単項のオペランドの場合です&またはsizeof)匿名配列への参照では、コンパイラはそれを最適化するかもしれません。

この場合、test自体は、指定した26文字と、'\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では、配列とポインタの間のしばしば混乱する関係について説明します。

27

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

未修正:

char * foo = "abc"; 

変更可能:

char foo[] = "abc"; 
+0

おっとり - 編集に感謝します。 – Joe

+1

foo [0] = 'x'はまだ私のボックスにセグメンテーションされています – pm100

4

あなたは初期化子の型に変数の型に一致するのが習慣に取得する必要があります。この場合:

const char* test = "abcdefghijklmnopqrstuvwxyz"; 

このようにして、実行時エラーではなくコンパイラエラーが発生します。コンパイラの警告レベルを最大にすることは、このような落とし穴を避けるのにも役立ちます。なぜこれがCのエラーではないのかはおそらく歴史的なものです。初期のコンパイラはそれを許可し、言語が標準化されたときに既存のコードがあまりにも壊れている可能性があることを拒否しました。しかしオペレーティングシステムではそれが許可されていないので学術的です。

3

文字列リテラルは変更できません。彼らがそうでないと仮定するのが最善です。詳細はhereを参照してください。

1

の操作を行います。

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

strdupは変更コピーを作成します。

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

+0

...そしてstrdup()を使うと最後にフリー(bar)! –

関連する問題