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

известно, что в C++ строковые литералы неизменяемы и результат изменения строкового литерала не определен. Например

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

это приведет к неопределенному поведению.

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

5 ответов


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

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

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

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

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

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

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


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


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

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

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

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

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

char *str="Hello";

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

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

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

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

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

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


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


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

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

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

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

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

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