C++ では文字列リテラルは不変であり、文字列リテラルを変更した結果は未定義であることが知られています。例えば
char * str = "Hello!";
str[1] = 'a';
これにより、未定義の動作が発生します。
それに加えて、文字列リテラルは静的メモリに配置されます。したがって、それらはプログラム全体に存在します。文字列リテラルにそのようなプロパティがある理由を知りたいです。
いくつかの異なる理由があります。
1つは、文字列リテラルを読み取り専用メモリに格納できるようにすることです(他の人がすでに述べたように)。
もう 1 つの方法は、文字列リテラルのマージを許可することです。1 つのプログラムが複数の異なる場所で同じ文字列リテラルを使用している場合、コンパイラがそれらをマージできるようにするとよいでしょう (必須ではありません)。これにより、それぞれが別々のメモリ チャンクを占有するのではなく、同じメモリへの複数のポインターを取得できます。これは、2 つの文字列リテラルが必ずしも同一ではないが、末尾が同じ場合にも適用できます。
char *foo = "long string";
char *bar = "string";
このような場合、(正しく数えていれば)可能性bar
があります。foo+5
どちらの場合でも、文字列リテラルの変更を許可すると、たまたま同じ内容を持つ他の文字列リテラルが変更される可能性があります。同時に、正直なところ、それを義務付けることにはあまり意味がありません-オーバーラップできる十分な文字列リテラルがあることは非常にまれであり、ほとんどの人はおそらく数十バイトを節約するためだけにコンパイラの実行を遅くしたいと考えています.かそこらの記憶。
最初の標準が作成されるまでに、これらの 3 つの手法 (およびおそらく他にもいくつか) をすべて使用するコンパイラが既に存在していました。文字列リテラルを変更することで得られる 1 つの動作を説明する方法がなく、それがサポートする重要な機能であると誰も考えていなかったので、彼らは明白なことを行いました。
標準がそう言っているので、リテラルを変更することは未定義の動作です。また、標準では、コンパイラがリテラルを読み取り専用メモリに入れることを許可するように言っています。そして、それはいくつかの理由でこれを行います。その 1 つは、ソースで何度も繰り返されるリテラルのインスタンスを 1 つだけ格納する最適化をコンパイラに許可することです。
リテラルが読み取り専用メモリに配置される理由について質問していると思いますが、これを行うリンカーの技術的な詳細や、これを禁止する標準の法的詳細についてではありません。
文字列リテラルの変更が機能する場合、文字列のマージがなくても微妙なバグが発生します (変更を許可することにした場合、これを許可しない理由があります)。のようなコードを見ると
char *str="Hello";
.../* some code, but str and str[...] are not modified */
printf("%s world\n", str);
初期化と使用の間の特定の場所で (およびその内容) が変更されていないため、何が印刷されるかを知っているというのは当然の結論です。str
ただし、文字列リテラルが書き込み可能である場合、それはもうわかりません: str[0] は後で、このコード内または深くネストされた関数呼び出し内で上書きされる可能性があり、コードが再度実行されると、
char *str="Hello";
の内容については一切保証しstr
ません。予想どおり、この初期化は、リンク時に既知のアドレスを の場所に移動することで実装されstr
ます。「Hello」が含まれていることを確認せstr
ず、その新しいコピーを割り当てません。ただし、このコードは「Hello」にリセットされると理解しています。str
この自然な理解を克服するのは難しく、保証されていないコードについて推論するのは困難です。のような式を見たとき
x+14
、14 が他のコードで上書きされる可能性があることを考えなければならないので、42 になったとしたらどうでしょうか。文字列と同じ問題。
これが、標準 (障害を早期に検出する必要がない) と実際のターゲット プラットフォーム (潜在的なバグを検出するボーナスを提供する) の両方で、文字列リテラルの変更を禁止する理由です。
このことを説明しようとする多くの試みは、最悪の種類の循環論法に苦しんでいると私は信じています. コンパイラが文字列をマージしたり、読み取り専用メモリに配置したりできるため、標準ではリテラルへの書き込みが禁止されています。これらは、標準の違反を検出するために読み取り専用メモリに配置されます。そして、標準では禁止されているため、リテラルをマージすることは有効です...それはあなたが求めた一種の説明ですか?
他の言語を見てみましょう。 以前の Lisp の歴史は C 実装の歴史と大きく異なっていても、Common Lisp 標準はリテラルの変更を未定義の動作にします。これは、書き込み可能なリテラルが論理的に 危険だからです。言語標準とメモリ レイアウトは、その事実のみを反映しています。
Python 言語には、「リテラルへの書き込み」に似たことが起こる場所が 1 つだけあります: パラメータのデフォルト値であり、この事実は常に人々を混乱させます。
あなたの質問はタグ付けされていますが、非 constへの暗黙的な変換C++
に関して現在の状態がわかりません:変換の場合、非推奨ですか? この点について、他の回答が完全な啓発を提供することを期待しています。ここで他の言語について話すときは、単純な C について言及させてください。ここでは、文字列リテラルはconst ではありません。同等の質問は、なぜ文字列リテラルを変更できないのかということです(より経験のある人は代わりに、なぜ文字列は変更できない場合、リテラルは const ではありませんか?)。ただし、この違いにもかかわらず、上記の推論は C に完全に適用できます。char*
K&R Cなので「const」というものはありませんでした。ANSI 以前の C++ でも同様です。char * str = "Hello!";
したがって、標準化委員会がテキスト リテラルを const にした場合、それらのプログラムはすべてコンパイルされなくなってしまうというようなコードがたくさんありました。そこで彼らは妥協しました。テキスト リテラルは正式なconst char[]
ものですが、サイレントに暗黙的に に変換されchar*
ます。
C++ では、文字列リテラルはconst
変更が許可されていないためです。標準 Cでも同様でしたが、C に導入されconst
たとき、それらを const にすると壊れてしまうほど多くのコードがあり、受け入れられないと見なされました。(C++ 委員会は、上記を可能にする非推奨の暗黙的な変換を使用して、この問題に対して別の解決策を選択しました。)const
char* p = "somethin";
元の C では、文字列リテラルはconst ではなく可変であり、2 つの文字列リテラルがメモリを共有しないことが保証されていました。これは重大なエラーであることがすぐに判明し、次のようなことが可能になりました。
void
mutate(char* p)
{
static char c = 'a';
*p = a ++;
}
そして別のモジュールで:
mutate( "hello" ); // Can't trust what is written, can you.
(Fortran の初期の実装のいくつかには、ほぼすべての整数値でF(4)
呼び出される可能性がある同様の問題がありF
ました。C 委員会が C の文字列リテラルを修正したのと同様に、Fortran 委員会はこれを修正しました。)