Почему строковые литералы const?

Известно, что в С++ строковые литералы неизменяемы, а результат модификации строкового литерала - undefined. Например

char * str = "Hello!";
str[1] = 'a';

Это приведет к поведению undefined.

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

Ответ 1

Есть несколько разных причин.

Один из них - разрешить сохранение строковых литералов в постоянной памяти (как уже упоминалось ранее).

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

char *foo = "long string";
char *bar = "string";

В таком случае возможно bar быть foo+5 (если бы я правильно подсчитал).

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

К моменту написания первого стандарта были уже составители, которые использовали все три из этих методов (и, возможно, несколько других, кроме того). Поскольку не было способа описать одно поведение, которое вы могли бы получить от модификации строкового литерала, и никто, по-видимому, не думал, что это важная возможность для поддержки, они сделали очевидное: сказал, что даже попытка сделать это привела к поведению undefined.

Ответ 2

Это поведение undefined для изменения литерала, потому что стандарт говорит об этом. И стандарт говорит так, чтобы компиляторы могли помещать литералы в память только для чтения. И это происходит по ряду причин. Один из них - позволить компиляторам оптимизировать сохранение только одного экземпляра литерала, который многократно повторяется в источнике.

Ответ 3

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

Когда модификация строковых литералов работает, это приводит к тонким ошибкам даже в отсутствие слияния строк (у нас есть основания для запретите, если мы решили разрешить изменение). Когда вы видите код типа

char *str="Hello";
.../* some code, but str and str[...] are not modified */
printf("%s world\n", str);

Это естественный вывод, что вы знаете, что собираетесь печатать, потому что str (и его содержимое) не были изменены в конкретном место, между инициализацией и использованием.

Однако, если строковые литералы доступны для записи, вы этого не знаете more: str [0] может быть перезаписана позже, в этом коде или внутри глубокий вложенный вызов функции, а когда код запускается снова,

char *str="Hello";

больше ничего не даст о содержании str. Как мы ожидаем, что эта инициализация реализована как перемещение известного адреса в течение времени ссылки в место для str. Он не проверяет, что str содержит "Hello" и не выделяет новую копию. Однако, мы понимаем этот код как сброс str в "Hello". Трудно преодолеть это естественное понимание, и трудно рассуждать о код, где он не гарантируется. Когда вы видите выражение типа x+14, что, если вам нужно было подумать о 14, возможно, переписанном в другом коде, так что стало 42? Та же проблема со строками.

Это причина отказа от модификации строковых литералов, как в стандарт (без каких-либо требований для обнаружения сбоя на ранней стадии) и в фактические целевые платформы (обеспечивающие бонус обнаружения потенциала ошибки).

Я считаю, что многие попытки объяснить эту вещь страдают от худший вид круговых рассуждений. Стандарт запрещает писать потому что компилятор может объединять строки, или их можно разместить в постоянной памяти. Они помещаются в постоянное запоминающее устройство, чтобы нарушение стандарта. И это справедливо, чтобы объединить литералы, потому что стандарт запрещает... это какое-то объяснение, которое вы просили?

Посмотрим на другие языки. Общий Lisp стандарт делает модификацию литералов undefined, хотя история предшествующих Lisps очень отличается от истории C Реализации. Это потому, что записываемые литералы логически опасно. Языковые стандарты и макеты памяти отражают только то, что факт.

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

Ваш вопрос помечен C++, и я не уверен в его текущем состоянии относительно неявного преобразования в не-const char*: если оно конверсия, она устарела? Я ожидаю, что другие ответы полное просветление по этому вопросу. Поскольку мы говорим о других языках здесь, позвольте мне упомянуть простую C. Здесь строковые литералы не const, и эквивалентный вопрос, который нужно задать, - это то, почему я не могу изменить строку литералы (и люди с большим опытом спрашивают вместо этого, почему строковые литералы не const, если я не могу их изменить?). Однако рассуждение выше полностью применимо к C, несмотря на эту разницу.

Ответ 4

Поскольку K & R C, не было такой вещи, как "const". И аналогично в pre-ANSI С++. Следовательно, было много кода, в котором были такие вещи, как char * str = "Hello!"; Если комитет по стандартам сделал текстовые литералы const, все эти программы больше не были бы скомпилированы. Поэтому они сделали компромисс. Текстовые литералы являются официальными const char[], но они имеют молчаливое неявное преобразование в char*.

Ответ 5

В С++ строковые литералы const, потому что вам запрещено для их изменения. В стандарте C они были бы const как хорошо, за исключением того, что когда const было введено в C, так много кода в строках char* p = "somethin";, что сделав их const, сломался бы, что это было сочтено неприемлемо. (Комитет С++ выбрал другое решение для эта проблема, с устаревшим неявным преобразованием, которое позволяет выше).

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

void
mutate(char* p)
{
    static char c = 'a';
    *p = a ++;
}

и в другом модуле:

mutate( "hello" );  //  Can't trust what is written, can you.

(Некоторые ранние реализации Fortran имели аналогичную проблему, где F(4) может называть F практически любым интегральным значением. Комитет Fortran зафиксировал это, как и комитет С фиксированные строковые литералы в C.)