Почему (только) некоторые компиляторы используют один и тот же адрес для идентичных строковых литералов?

https://godbolt.org/z/cyBiWY

Я вижу два 'some' литералы в ассемблере, сгенерированные MSVC, но только один с clang и gcc. Это приводит к совершенно разным результатам выполнения кода.

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";

86

Решение

Это не неопределенное поведение, а неопределенное поведение. За строковые литералы,

Компилятору разрешено, но не обязательно, объединять хранилище для одинаковых или перекрывающихся строковых литералов. Это означает, что идентичные строковые литералы могут сравниваться или не совпадать при сравнении по указателю.

Это означает, что результат A == B возможно true или же false, от которого ты не должен зависеть.

Из стандарта, [Lex.string] / 16:

Все ли строковые литералы различны (то есть хранятся ли в неперекрывающихся объектах) и дают ли последовательные вычисления строкового литерала один и тот же или другой объект не определено.

106

Другие решения

Другие ответы объяснили, почему вы не можете ожидать, что адреса указателей будут другими. Тем не менее, вы можете легко переписать это так, чтобы гарантировать, что A а также B не сравнивайте равных:

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

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

Разница в том, что A а также B теперь массивы символов. Это означает, что они не являются указателями, и их адреса должны отличаться, как и адреса двух целочисленных переменных. C ++ смущает это, потому что указатели и массивы кажутся взаимозаменяемыми (operator* а также operator[] похоже, ведут себя одинаково), но они действительно разные. Например. что-то вроде const char *A = "foo"; A++; совершенно законно, но const char A[] = "bar"; A++; нет.

Один из способов думать о разнице заключается в том, что char A[] = "..." говорит: «дай мне блок памяти и заполните его персонажами ... с последующим \0«, в то время как char *A= "..." говорит «дай мне адрес, по которому я могу найти персонажей ... с последующим \0».

35

Независимо от того, решит ли компилятор использовать одно и то же расположение строки для A а также B до реализации. Формально вы можете сказать, что поведение вашего кода неопределенные.

Оба варианта правильно реализуют стандарт C ++.

22

Это оптимизация для экономии места, часто называемая «объединением строк». Вот документы для MSVC:

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

Поэтому, если вы добавите / GF в командную строку, вы должны увидеть то же поведение с MSVC.

Кстати, вы, вероятно, не должны сравнивать строки с помощью таких указателей, любой приличный инструмент статического анализа помечает этот код как дефектный. Вам нужно сравнить то, на что они указывают, а не фактические значения указателя.

3
По вопросам рекламы [email protected]