Почему (только) некоторые компиляторы используют один и тот же адрес для идентичных строковых литералов? - PullRequest
0 голосов
/ 15 октября 2018

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

Ответы [ 4 ]

0 голосов
/ 16 октября 2018

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

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

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

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

0 голосов
/ 15 октября 2018

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

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

0 голосов
/ 16 октября 2018

Другие ответы объясняли, почему вы не можете ожидать, что адреса указателей будут другими.Тем не менее, вы можете легко переписать это так, чтобы гарантировать, что 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 ».

0 голосов
/ 15 октября 2018

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

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

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

От стандарта [lex.string] / 16 :

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

...