Часто используемые редко используемые термины: lvalue

Что такое lvalue?

8 ответов

Lvalue - это значение, которое может быть присвоено:

lvalue = rvalue;

Это сокращение от "левое значение" или "левое значение", и это в основном просто значение слева от = знак, то есть значение, которое вы назначаете что-то.

В качестве примера того, что не является lvalue (то есть только rvalue):

printf("Hello, world!\n") = 100; // WTF?

Этот код не работает, потому что printf() (функция, которая возвращает int) не может быть lvalue, только rvalue.

Что-то, что появляется слева от назначения, то есть то, что может быть назначено.

Обратите внимание, что в C++ вызов функции может быть lvalue, если:

int & func() {
   static int a = 0;
   return a;
}

затем:

func() = 42;     // is legal (and not uncommon)

Это традиционно левая часть оператора "=". Однако со временем значение "lvalue"/"rvalue" изменилось. В C++ добавлен термин "немодифицируемое значение lvalue", то есть любое значение lvalue, которое не может быть присвоено: массивы и переменные, для которых задано значение "const", являются двумя примерами. В C вы не можете присвоить никакое значение (см. Ниже). Аналогично, в C++ вы не можете назначать значения, которые не принадлежат к определенному пользователем типу класса.

Вы можете сказать, что "lvalue" - это выражение, которое называет объект, который сохраняется со временем и занимает некоторое место в хранилище. Независимо от того, можете ли вы присвоить это выражение, не важно для этой классификации. Ссылка, в частности, также является lvalue, потому что она имеет имя, которое сохраняется с течением времени. Все последующие являются lvalues, потому что все они ссылаются на именованные объекты. Также обратите внимание, что const не влияет на lvalue-ность.

int a; lvalue: a;
       lvalue: ++a;
int a[2]; lvalue: a;
int &ra = a; lvalue: ra;
int *pa = &a; lvalue: *pa;

Термин "rvalue" используется для таких вещей, как литералы и значения перечислителей, а также для временных, которые не получают удовольствия от долгой жизни и сразу же уничтожаются в конце полного выражения. Для значений важен не аспект настойчивости, а аспект ценности. Функции в C++ являются lvalues, потому что они являются постоянными и имеют адрес, даже если они не являются объектами. Я пропустил их в приведенном выше обзоре lvalues, потому что легче понять lvalues, когда сначала только учитываем объекты. Все следующие являются значениями:

enum { FOO, BAR }; rvalue: BAR;
int a[2]; rvalue: (a+1);
rvalue: 42;
int a; rvalue: a++; // refering to a temporary
struct mystruct { }; mystruct f() { return mystruct(); } rvalue: f();

Между прочим, часто у вас есть lvalue, но оператору нужно rvalue. Например, двоичный встроенный оператор "+" добавляет два значения. Выражение lvalue first и for all указывает место, где значение должно быть сначала считано. Поэтому, когда вы добавляете две переменные, происходит преобразование "lvalue в rvalue". Стандарт говорит, что значение, содержащееся в выражении lvalue, является его результатом rvalue:

int a = 0, b = 1;
int c = a + b; // read values out of the lvalues of a and b. 

Другие операторы принимают не значение, а значение. Они не читают значение. Примером является адрес оператора, &, Вы не можете взять адрес rvalue выражений. Некоторые значения даже не являются объектами: они не занимают места в хранилище. Примерами снова являются литералы (10, 3.3, ...) и значения перечислителя.

Чем полезны эти страшные вещи?

Ну, у этого есть несколько преимуществ, чтобы иметь различие между lvalue и rvalue

  • Разрешение компилятору отказаться от использования памяти для значений r и использования регистров / только для чтения для скалярных значений
  • Пометка выражений как неуловимых: rvalues ​​не проживет долго
    • Обеспечивает эффективную семантику копирования для компилятора внутри и в C++1x, также предоставляемую программисту (см. Семантику перемещения и ссылки на rvalue): мы можем украсть ресурсы из значений r, которые в любом случае будут уничтожены.
  • Позволяет строить правила на этом свойстве
    • Не допускается создание значений r из еще неинициализированных объектов, на которые ссылается значение l. Но lvalues ​​может относиться к неинициализированным объектам просто отлично
    • Значения никогда не могут быть полиморфными. Их статический тип также должен быть их динамическим типом: упрощает правила для оператора typeid.

... это еще не все, я чувствую это...

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

Другой способ определить, является ли выражение lvalue, - спросить "могу ли я взять его адрес?". Если вы можете, это lvalue. Если вы не можете, это ценность. Например, &obj, &*ptr, &ptr[index] и & ++ x допустимы (хотя некоторые из этих выражений глупы), а &1729, &(x + y), &std::string("meow") и & x ++ недопустимы. Почему это работает? Оператор address-of требует, чтобы его "операнд был lvalue" (C++03 5.3.1/2). Почему это требует этого? Взять адрес постоянного объекта - это хорошо, но взять временный адрес было бы чрезвычайно опасно, потому что временные испарения быстро испаряются.

Буква "L" в lvalue обычно обозначается как "местоположение". Lvalue указывает местоположение чего-либо; как указал другой ответчик, lvalues ​​обычно может получить свой адрес. Вот почему числовые литералы и возвращаемые не ссылочные функции значения не являются lvalues.

L использовался для обозначения "left", пока const не был введен в C++. Постоянные значения не могут отображаться в левой части назначения.

Для языка программирования C в C11 draft n1570 6.3.2.1p1 выражается кратко:

Lvalue - это выражение (с типом объекта, отличным от void), которое потенциально обозначает объект [...]

Это так просто.

Определение Википедии в день написания этого ответа было почти таким же хорошим

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


Примеры выражений lvalue? Дано

int a, b[1], *c;

у нас могут быть следующие выражения lvalue: a, b а также c каждый из которых определенно обозначает объект. b[0] также обозначает объект, следовательно, это выражение lvalue. А как насчет потенции? *c всегда является выражением lvalue, но оно не обозначает объект, если c не содержит указатель на действительный объект. b[1] может семантически определить объект, но это не так, потому что он обращается к массиву за пределами. Оба они являются выражениями lvalue.

Когда вычисляется выражение lvalue, которое фактически не обозначает объект, поведение не определено.

Выражение lvalue может быть более сложным, например:

((flag ? foo() : bar())->pointed_to_by_struct_member[42])[0] 

если он компилируется, это выражение lvalue.

В основном все, что вы можете применить & (адрес оператора) to в C является l-значением, кроме функций, поскольку функции не являются объектами в C.

Так что же тогда означает L?

C11 n1570 Сноска 64:

64) Имя lvalue происходит от выражения присваивания E1 = E2, в котором левый операнд E1 должен быть (изменяемым) lvalue. Возможно, это лучше рассматривать как представление значения локатора объекта. [...]


Заметить, что E1 = E2 не требуется компилировать для E1 быть lvalue в ISO C. 6.3.2.1p1 продолжает:

Модифицируемое lvalue - это lvalue, которое не имеет типа массива, не имеет неполного типа, не имеет const -квалифицированный тип, и если это структура или объединение, не имеет какого-либо члена (включая, рекурсивно, любой член или элемент всех содержащихся агрегатов или объединений) с const квалифицированный тип.

Раньше в языках до C (например, B) все значения локатора могли появляться в левой части присваивания, отсюда и название. Это больше не относится к Си, так как массивы никогда не могли быть назначены, а ISO C добавлен const,


PS Та же самая сноска объясняет связанный термин rvalue следующим образом:

[...] То, что иногда называют rvalue, в этом международном стандарте описывается как значение выражения. [...]

Простой пример того, что определенно не является lvalue:

3 = 42;

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

Объект - это область хранения, которая может быть исследована и сохранена в. Lvalue - это выражение, которое относится к такому объекту. Значение l не обязательно допускает изменение объекта, который оно обозначает. Например, const-объект - это lvalue, которое нельзя изменить. Термин "модифицируемое значение lvalue" используется для того, чтобы подчеркнуть, что значение lvalue позволяет изменять обозначенный объект, а также проверять его. Следующие типы объектов являются lvalues, но не могут быть изменены lvalues:

  • Тип массива
  • Неполный тип
  • Конст-квалифицированный тип
  • Объект является структурой или типом объединения, и один из его членов имеет тип с константой

Поскольку эти l-значения не могут быть изменены, они не могут появляться с левой стороны оператора присваивания.

В C++ вызов функции, которая возвращает ссылку, является lvalue. В противном случае вызов функции является выражением rvalue. В C++ каждое выражение создает lvalue, rvalue или значение без значения.

Некоторые операторы требуют lvalues ​​для некоторых из своих операндов. В таблице ниже перечислены эти операторы и дополнительные ограничения на их использование.

     Требование оператора и (унарный) операнд должны быть lvalue.
     ++ - операнд должен быть lvalue. Это относится как к префиксным, так и к постфиксным формам.
     = += -= *= %= >= &= ^= |= Левый операнд должен быть l-значением.

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

Оператор адреса (&) требует lvalue в качестве операнда, в то время как операторы увеличения (++) и декремента (-) требуют изменяемого lvalue в качестве операнда.

Другие вопросы по тегам