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

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";
stackoverflow.com/a/52424271/1133179 Хороший релевантный ответ на тесно связанный вопрос со стандартными цитатами.
luk32 15.10.2018 13:36

Для MSVC параметр компилятора / GF управляет этим поведением. См. docs.microsoft.com/en-us/cpp/build/reference/…

Sjoerd 15.10.2018 16:01

К вашему сведению, это может случиться и с функциями.

user541686 15.10.2018 21:01

Также работает для "некоторой строки" и "строки" на некоторых компиляторах.

PlasmaHH 16.10.2018 12:40

Интересно, что поведение в GCC при нескольких разных оптимизациях выглядит одинаково!

Owl 16.10.2018 16:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
92
8
7 429
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

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

The compiler is allowed, but not required, to combine storage for equal or overlapping string literals. That means that identical string literals may or may not compare equal when compared by pointer.

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

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

Whether all string literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified.

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

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

Поведение кода - либо генерировать исключение, либо ничего не делать, выбрано до первого выполнения кода неопределенным образом. Это не означает, что поведение в целом не определено - просто компилятор может выбрать любое поведение любым способом, который он сочтет подходящим, до первого наблюдения за поведением.

supercat 16.10.2018 19:53

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

Это был бы еще лучший ответ, если бы вы могли объяснить Почему, что это другое.

Mark Ransom 16.10.2018 06:12

Обратите внимание, что *p и p[0] не только «кажутся одинаковыми», но по определению идентичны находятся (при условии, что p+0 == p является отношением идентичности, потому что 0 является нейтральным элементом в сложении указателя и целого числа). В конце концов, p[i] определяется как *(p+i). Тем не менее, ответ является хорошим подспорьем.

Peter - Reinstate Monica 16.10.2018 15:03
typeof(*p) и typeof(p[0]) оба являются char, так что на самом деле осталось немногое, что могло бы отличаться. Я согласен с тем, что «кажется, вести себя одинаково» - не лучшая формулировка, потому что семантика очень разная. Ваш пост напомнил мне о лучшем способе доступа к элементам массивов C++: 0[p], 1[p], 2[p] и т. д. Так делают профессионалы, по крайней мере, когда они хотят запутать людей, родившихся после языка программирования C.
tobi_s 16.10.2018 16:14

Это интересно, и у меня возникло желание добавить ссылку на FAQ по C, но я понял, что есть много связанных вопросов, но ни один, похоже, не подходит к сути этого вопроса здесь.

tobi_s 17.10.2018 05:48

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

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

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

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

Другие вопросы по теме