Подтвердить что ты не робот

Почему строковые литералы l-value, а все другие литералы - r-значение?

С++ 03 5.1 Первичные выражения
§2:

Литерал является основным выражением. Его тип зависит от его формы (2.13). Строковый литерал - это значение lvalue; все другие литералы - это значения.

В чем причина этого? Как я понимаю, строковые литералы - это объекты, а все остальные литералы - нет. И l-значение всегда относится к объекту.

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

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

Примечание. Я помещаю этот вопрос как c и С++, так как стандарт C99 также имеет похожие цитаты, в частности §6.5.1.4

4b9b3361

Ответ 1

Строковый литерал - это литерал с типом массива, а в C нет способа существования типа массива в выражении, кроме как lvalue. Строковые литералы могли быть указаны с типом указателя (а не типа массива, который обычно распадается на указатель), указывая на строку "содержимое", но это сделало бы их менее полезными; в частности, оператор sizeof не может быть применен к ним.

Обратите внимание, что C99 вводил сложные литералы, которые также являются lvalues, поэтому наличие буквального значения lvalue больше не является особым исключением; это ближе к норме.

Ответ 2

Значение lvalue в С++ не всегда относится к объекту. Он также может ссылаться на функцию. Более того, объекты не обязательно должны ссылаться на lvalues. На них можно ссылаться на rvalues, в том числе на массивы (на С++ и C). Однако в старом C89 преобразование массива в указатель не применялось для массивов rvalues.

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

Таким образом, строковые литералы являются значениями lvalues.

Ответ 3

Я бы предположил, что первоначальный мотив был в основном прагматичным: строка literal должен находиться в памяти и иметь адрес. Тип строки literal - тип массива (char[] в C, char const[] в С++) и типы массивов конвертируются в указатели в большинстве контекстов. Язык может нашли другие способы определения этого (например, строковый литерал мог иметь тип указателя для начала, со специальными правилами относительно того, что это указали на), но просто сделать литерал lvalue, вероятно, самый простой способ определения того, что конкретно необходимо.

Ответ 4

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

Любые попытки искусственно скрыть тот факт, что строковые литералы являются значениями на уровне языка, приведут к значительному количеству совершенно ненужных трудностей, поскольку способность указывать на строковый литерал с указателем, а также возможность доступа к нему как массив полагается критически на его lvalue-ness быть видимым на уровне языка.

Между тем, литералы скалярных типов имеют фиксированный размер времени компиляции. В то же время такие литералы, скорее всего, будут встроены непосредственно в команды машины на данной аппаратной архитектуре. Например, когда вы пишете что-то вроде i = i * 5 + 2, литеральные значения 5 и 2 становятся явными (или даже неявными) частями генерируемого машинного кода. Они не существуют и не должны существовать как автономные места хранения данных. Там нет смысла хранить значения 5 и 2 в памяти данных.

Также не стоит ничего, что на многих (если не большинство или все) аппаратных архитектурах с плавающей запятой буквально реализуются как "скрытые" значения (хотя язык не раскрывает их как таковые). На таких платформах, как машинные команды 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;

Другими словами, литералы с плавающей запятой часто становятся "неофициальными" lvalues ​​внутри. Тем не менее, совершенно очевидно, что спецификация языка не делала попыток разоблачить эту деталь реализации. На уровне языка арифметические литералы имеют больше смысла в качестве значений.