web-dev-qa-db-ja.com

同じコンテンツ文字列リテラルのストレージは同じであることが保証されていますか?

以下のコードは安全ですか?これに似たコードを書くのは魅力的かもしれません:

#include <map>

const std::map<const char*, int> m = {
    {"text1", 1},
    {"text2", 2}
};

int main () {
    volatile const auto a = m.at("text1");
    return 0;
}

マップは、文字列リテラルのみで使用することを目的としています。

完全に合法であり、機能しているように見えますが、2つの異なる場所で使用されるリテラルのポインターが同じであるという保証はありませんでした。コンパイラに同じ内容のリテラル用の2つの個別のポインターを生成させることができなかったので、仮定がどれほどしっかりしているのか疑問に思い始めました。

同じ内容のリテラルが異なるポインターを持つことができるかどうかにのみ興味があります。またはより正式には、上記のコードは例外ですか?

確実に動作するようにコードを書く方法があることを知っています。コンパイラがリテラルに2つの異なるストレージを割り当てることを決定できるため、上記のアプローチは危険だと思います。私は正しいですか?

24
luk32

標準は、同じ内容の文字列リテラルのアドレスが同じであることを保証しません。実際、 [Lex.string]/16 はこう言います:

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

2番目の部分は、文字列リテラルを含む関数が2回目に呼び出されたときに、同じアドレスを取得できない可能性があることさえ示しています。コンパイラーがそれをするのを見たことがありませんが。

そのため、文字列リテラルが繰り返されるときに同じ文字配列オブジェクトを使用することは、オプションのコンパイラ最適化です。 g ++とデフォルトのコンパイラフラグをインストールすると、同じ変換ユニット内の2つの同一の文字列リテラルに対して同じアドレスが取得されます。しかし、ご想像のとおり、同じ文字列リテラルコンテンツが異なる翻訳単位に表示される場合、異なる文字列が表示されます。


関連する興味深い点:異なる文字列リテラルが重複する配列を使用することも許可されています。つまり、与えられた

const char* abcdef = "abcdef";
const char* def = "def";
const char* def0gh = "def\0gh";

abcdef+3def、およびdef0ghはすべて同じポインターです。

また、文字列リテラルオブジェクトの再利用または重複に関するこの規則は、リテラルがポインターにすぐに減衰するか、配列への参照にバインドされる場合に使用される、リテラルに直接関連付けられた名前のない配列オブジェクトにのみ適用されます。リテラルは、次のように名前付き配列を初期化するためにも使用できます。

const char a1[] = "XYZ";
const char a2[] = "XYZ";
const char a3[] = "Z";

ここで、配列オブジェクトa1a2およびa3はリテラルを使用して初期化されますが、実際のリテラルストレージ(存在する場合)とは異なると見なされ、通常のオブジェクトルールに従うため、これらの配列のストレージは重複しません。

18
aschepler

まったく同じ内容の2つの文字列リテラルがまったく同じオブジェクトであるかどうかは未指定であり、私の意見ではこれに依存しないことが最善です。標準を引用するには:

[Lex.string]

16 文字列リテラルを評価すると、上記のように指定された文字から初期化された、静的ストレージ期間を持つ文字列リテラルオブジェクトが生成されます。すべての文字列リテラルが異なる(つまり、重複しないオブジェクトに格納される)かどうか、および文字列リテラルの連続した評価で同じオブジェクトまたは異なるオブジェクトが得られるかどうかは指定されていません。

std::stringのオーバーヘッドを回避する場合は、文字列リテラルに対する参照型である単純なビュー型を記述できます(またはC++ 17でstd::string_viewを使用します)。リテラルIDに依存する代わりに、インテリジェントな比較を行うために使用します。

20
StoryTeller

いいえ、C++標準ではそのような保証はありません。

つまり、コードが同じ翻訳単位にある場合、反例を見つけるのは難しいでしょう。 main()が異なる翻訳にある場合、反例の方が簡単に作成できます。

マップが別のダイナミックリンクライブラリまたは共有オブジェクトにある場合、ほぼ確実にそうではありません。

volatile修飾子は赤いニシンです。

5
Bathsheba

C++標準では、文字列リテラルを重複排除するための実装は必要ありません。

文字列リテラルが、リンカ(ld)または実行時リンカー(ld.so)文字列リテラルの重複排除を行います。彼らはしません。

3