Почему строковые литералы имеют l-значение, а все остальные литералы имеют r-значение?
C++03 5.1 Основные выражения
§2:
Литерал - это первичное выражение. Его тип зависит от его формы (2.13). Строковый литерал является lvalue; все остальные литералы являются значениями.
В чем причина этого?
Как я понимаю, строковые литералы являются объектами, а все остальные литералы - нет. И l-значение всегда ссылается на объект.
Но тогда возникает вопрос: почему строковые литералы являются объектами, а все остальные литералы - нет?
Это обоснование кажется мне больше похожим на проблему с яйцом или курицей.
Я понимаю, что ответ на этот вопрос может быть связан с аппаратной архитектурой, а не с C/C++ как языками программирования, тем не менее, я бы хотел услышать то же самое.
Примечание: я помечаю этот вопрос как c & C++, так как стандарт C99 также имеет схожие цитаты, в частности §6.5.1.4
5 ответов
Строковый литерал - это литерал с типом массива, и в Си нет никакого способа для того, чтобы тип массива существовал в выражении, кроме как в виде lvalue. Можно указать, что строковые литералы имеют тип указателя (а не тип массива, который обычно распадается на указатель), указывающий на строку "содержимое", но это сделало бы их менее полезными; в частности, sizeof
оператор не может быть применен к ним.
Обратите внимание, что C99 ввел составные литералы, которые также являются lvalue, поэтому наличие литерала be lvalue больше не является специальным исключением; это ближе к тому, чтобы быть нормой.
Строковые литералы - это массивы - объекты непредсказуемого размера (т.е. определенного пользователем и, возможно, большого размера). В общем случае просто нет другого способа представить такие литералы, кроме как объекты в памяти, то есть как lvalues
, В C99 это также относится к составным литералам, которые также lvalues
,
Любые попытки искусственно скрыть тот факт, что строковые литералы lvalues
на уровне языка создаст значительное количество совершенно ненужных трудностей, поскольку способность указывать на строковый литерал с указателем, а также возможность доступа к нему в виде массива в значительной степени зависит от его значимости, видимой на уровне языка,
Между тем литералы скалярных типов имеют фиксированный размер во время компиляции. В то же время такие литералы с большой вероятностью могут быть встроены непосредственно в машинные команды на данной аппаратной архитектуре. Например, когда вы пишете что-то вроде i = i * 5 + 2
, буквальные значения 5
а также 2
стать явными (или даже неявными) частями сгенерированного машинного кода. Они не существуют и не должны существовать как отдельные места в хранилище данных. Там просто нет смысла хранить значения 5
а также 2
в памяти данных.
Стоит также отметить, что на многих (если не на большинстве или на всех) аппаратных архитектурах литералы с плавающей точкой фактически реализованы как "скрытые" lvalues
(хотя язык не выставляет их как таковые). На таких платформах, как x86, машинные команды из группы с плавающей запятой не поддерживают встроенные непосредственные операнды. Это означает, что практически каждый литерал с плавающей точкой должен храниться (и считываться) из памяти данных компилятором. Например, когда вы пишете что-то вроде i = i * 5.5 + 2.1
это переводится в нечто вроде
const double unnamed_double_5_5 = 5.5;
const double unnamed_double_2_1 = 2.1;
i = i * unnamed_double_5_5 + unnamed_double_2_1;
Другими словами, floating-point literals
часто в конечном итоге становится "неофициальным" lvalues
внутренне. Однако вполне логично, что спецификация языка не делала попыток раскрыть детали реализации. На уровне языка, arithmetic literals
иметь больше смысла, как rvalues
,
lvalue
в С ++ не всегда ссылаются на объект. Это может относиться и к функции. Кроме того, объекты не должны быть упомянуты lvalues
, На них могут ссылаться rvalues
в том числе для массивов (в C++ и C). Однако в старом C89 преобразование массива в указатель не применялось для rvalues
массивы.
Теперь rvalue
обозначает нет, ограниченный или скоро истекший срок жизни. Однако строковый литерал живет для всей программы.
Так string literals
являющийся lvalues
совершенно верно.
Я предполагаю, что первоначальный мотив был в основном прагматичным: строковый литерал должен находиться в памяти и иметь адрес. Тип строкового литерала является типом массива (char[]
в С, char const[]
в C++), а типы массивов в большинстве случаев преобразуются в указатели. Язык мог бы найти другие способы определить это (например, строковый литерал мог бы иметь для начала тип указателя со специальными правилами, касающимися того, на что он указывал), но просто сделать литерал lvalue, возможно, самым простым способом определения того, что конкретно необходимо.
В ответах и комментариях много ценной информации. Стоит выделить несколько моментов.
Массивы могут быть значениями rvalue. Более подробную информацию можно найти здесь и здесь . Например, следующий код включает массив rvalue:
template <typename T>
using alias = T;
int main() {
return alias<int[]>{23, 37, 53}[1];
}
Таким образом, не стоит рассуждать о том, что строковые литералы являются массивами, поскольку они являются lvalue.
Полезно помнить, что строковые литералы сохраняются на протяжении всего времени существования программы. Несмотря на то, что категория значений не является временем жизни , имеет смысл понять, почему строковые литералы являются lvalue на основе их времени жизни.
Как и во многих дискуссиях о категориях значений, строковые литералы, являющиеся l-значениями, во многом обусловлены прагматическими соображениями о том, что произошло в развитии языка до сих пор и что лучше всего можно сделать с того места, где мы находимся в данный момент времени.