一部のコンパイラーが同一の文字列リテラルに同じアドレスを使用するのはなぜですか?


92

https://godbolt.org/z/cyBiWY

'some'MSVCによって生成されたアセンブラコードで2つのリテラルを確認できますが、clangとgccを使用するリテラルは1つだけです。これにより、コード実行の結果がまったく異なります。

static const char *A = "some";
static const char *B = "some";

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

誰かがそれらのコンパイル出力の違いと類似点を説明できますか?最適化が要求されていないのに、clang / gccが何かを最適化するのはなぜですか?これはある種の未定義の動作ですか?

また、宣言を以下に示すように変更すると、clang / gcc / msvcはアセンブラー"some"コードに何も残さないことに気づきました。なぜ動作が異なるのですか?

static const char A[] = "some";
static const char B[] = "some";

4
stackoverflow.com/a/52424271/1133179密接に関連する質問に対するいくつかのすばらしい関連する回答と標準的な引用。
luk32


6
MSVCの場合、/ GFコンパイラオプションがこの動作を制御します。docs.microsoft.com/en-us/cpp/build/reference/…を
Sjoerd

1
参考までに、これは関数でも発生する可能性があります。
user541686

回答:


109

これは未定義の動作ではなく、未指定の動作です。以下のための文字列リテラル

コンパイラは、等しいまたは重複する文字列リテラルのストレージを組み合わせることができますが、必須ではありません。つまり、ポインタで比較したときに、同じ文字列リテラルが等しいかどうかは異なります。

つまり、の結果はor であるA == B可能性があり、依存するべきではありません。truefalse

標準から、[lex.string] / 16

すべての文字列リテラルが別個である(つまり、重複しないオブジェクトに格納されている)かどうか、および文字列リテラルの連続した評価で同じオブジェクトまたは異なるオブジェクトが生成されるかどうかは指定されていません。


36

他の回答は、ポインタアドレスが異なると期待できない理由を説明しています。しかし、これを保証しAB同等と比較しない方法でこれを簡単に書き換えることができます。

static const char A[] = "same";
static const char B[] = "same";// but different

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

違いがあることであることABなりました文字の配列です。つまり、これらはポインタではなく、2つの整数変数のアドレスと同じように、アドレスを区別する必要があります。それは、ポインタと配列は、交換可能に見える(なるためC ++はこれを混乱させるoperator*operator[]同じように動作しているように見える)が、彼らは本当に異なっています。たとえば、のようなものconst char *A = "foo"; A++;は完全に合法ですが、そうでconst char A[] = "bar"; A++;はありません。

違いを考える1つの方法は、char A[] = "..."「メモリブロックを与えて、...それに続く文字を入力してください」と言うの\0に対して、「char *A= "..."文字が...続く文字を見つけることができるアドレスを教えて」と言い\0ます。


8
これが異なる理由を説明できれば、これはさらに良い答えになります。
Mark Ransom、

なお*pp[0]だけでなく、「同じように動作するように見える」が、定義によってある(が提供同じp+0 == pため、同一の関係である0ポインタ整数加えて中性元素です)。結局のところ、p[i]はとして定義されてい*(p+i)ます。答えは良い点ですが。
ピーター-モニカを復活させる'16年

typeof(*p)typeof(p[0])両方なcharので、異なる可能性のあるものはほとんど残っていません。セマンティクスが非常に異なるため、「同じように振る舞うように見える」が最良の表現ではないことに同意します。:あなたの投稿は、C ++の配列のアクセス要素への最善の方法のことを思い出し0[p]1[p]2[p]彼らはCプログラミング言語の後に生まれた人々を混乱させたいときには、少なくとも、プロがそれを行う方法であるなど。
tobi_s 2018年


これは興味深いもので、C FAQへのリンクを追加したくなりましたが、関連する質問がたくさんあることに気づきましたが、ここでこの質問のポイントに的を絞ったものはないようです。
tobi_s 2018年

23

コンパイラが同じ文字列の場所を使用することを選択するかどうか、AおよびB実装次第です。正式には、コードの動作は不特定であると言えます

どちらを選択しても、C ++標準が正しく実装されます。


コードの動作は、コードが最初に実行される前に、指定されていない方法で、例外をスローするか、選択した何もしないかのいずれかです。これは、全体としての動作が指定されていないことを意味するわけではありません。単に、動作が初めて観察される前に、コンパイラが適切と思われる方法でどちらかの動作を選択できるということです。
スーパーキャット2018年

3

これは、スペースを節約するための最適化であり、「ストリングプーリング」と呼ばれます。MSVCのドキュメントは次のとおりです。

https://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

したがって、コマンドラインに/ GFを追加すると、MSVCで同じ動作が見られるはずです。

ちなみに、おそらくそのようなポインターを介して文字列を比較するべきではありません。適切な静的分析ツールは、そのコードに欠陥があるとフラグを立てます。実際のポインタ値ではなく、それらが指すものを比較する必要があります。

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