Является ли хранилище для тех же литературных строк, которые гарантируют, что они будут одинаковыми?

Безопасен ли код? Может возникнуть соблазн написать код, похожий на это:

#include <map>

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

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

Карта предназначена для использования только со строковыми литералами.

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

Меня интересует только то, имеют ли литералы с одним контентом разные указатели. Или более формально, может ли код выше, кроме?

Я знаю, что есть способ написать код, чтобы убедиться, что он работает, и я думаю, что выше подход опасен, потому что компилятор может решить назначить два разных хранилища для литерала, особенно если они помещены в разные единицы перевода. Я прав?

Ответ 1

Стандарт не гарантирует, что адреса строковых литералов с одним и тем же содержимым будут одинаковыми. Фактически, [lex.string]/16 говорит:

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

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

Таким образом, использование одного и того же символьного массива, когда повторяется строковый литерал, является необязательной оптимизацией компилятора. С моей установкой флагов компилятора g++ и по умолчанию я также обнаружил, что получаю один и тот же адрес для двух одинаковых строковых литералов в одной и той же системе переводов. Но, как вы догадались, я получаю разные, если одно и то же строковое литеральное содержимое появляется в разных единицах перевода.


Связанный интересный момент: он также допускал, чтобы различные строковые литералы использовали перекрывающиеся массивы. То есть, учитывая

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

возможно, вы можете найти abcdef+3, def и def0gh - все те же указатели.

Кроме того, это правило о повторном использовании или перекрытии объектов строкового литерала применяется только к объекту без имени, непосредственно связанному с литералом, который используется, если литерал немедленно распадается на указатель или привязан к ссылке на массив. Литерал может также использоваться для инициализации именованного массива, как в

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

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

Ответ 2

Независимо от того, являются ли два строковых литерала с одним и тем же содержимым точным одним и тем же объектом, является неуказанным и, на мой взгляд, лучше всего не полагаться. Процитировать стандарт:

[lex.string]

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

Если вы хотите избежать накладных расходов на std::string, вы можете написать простой тип представления (или использовать std::string_view в С++ 17), который является ссылочным типом над строковым литералом. Используйте его для интеллектуальных сравнений вместо того, чтобы полагаться на литеральную идентификацию.

Ответ 3

Нет, стандарт C++ не дает таких гарантий.

Тем не менее, если код находится в одной и той же единице перевода, было бы сложно найти встречный пример. Если main() находится в другом переводе, то пример счетчика может быть проще создать.

Если карта находится в другой динамической связанной библиотеке или общему объекту, то это почти наверняка не так.

volatile классификатор - красная селедка.

Ответ 4

Стандарт C++ не требует реализации для удаления дубликатов строковых литералов.

Когда строковый литерал находится в другой единицы перевода или другой разделяемой библиотеке, для которой требуется компоновщик (ld) или runtime-linker (ld.so) для выполнения ld.so строкового литерала. Что они не делают.