web-dev-qa-db-ja.com

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

https://godbolt.org/z/cyBiWY

MSVCによって生成されたアセンブラコードには2つの'some'リテラルがありますが、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";
87
Eugene Kosov

これは未定義の動作ではなく、未定義の動作です。 文字列リテラルの場合

コンパイラーは、等しいまたは重複するストリング・リテラルのために記憶域を組み合わせることを許可されていますが、必須ではありません。これは、同一の文字列リテラルは、ポインタで比較したときに等しいとは限らないことを意味します。

これは、A == Bの結果がtrueまたはfalseになる可能性があることを意味しますが、これには依存しないでください。

標準より、 [Lex.string]/16

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

106
songyuanyao

他の回答では、ポインタアドレスが異なることを期待できない理由を説明しました。ただし、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が続く文字を見つけることができるアドレスを教えてください。

35
tobi_s

コンパイラがABに同じ文字列位置を使用するかどうかは、実装次第です。正式には、コードの振る舞いは 未指定 であると言えます。

どちらの選択もC++標準を正しく実装しています。

22
Bathsheba

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

https://msdn.Microsoft.com/ja-jp/library/s0s0asdt.aspx

したがって、/ GFをコマンドラインに追加すると、MSVCでも同じ動作になります。

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

3
paulm