In C, is there a way to identify the rvalues and lvalues ?
Some of them are easy to identity say, in an assignment, the left value is lvalue and the value in the right is rvalue.
But other scenarios, identification with such a rule is difficult.
For example : *p++
and i++
(where p is a pointer to integer and i is an integer) - how to identify whether it is an rvalue or lvalue ?
The context is ++*p++
works while ++i++
does not since i++
is an rvalue (as told by serious guys).
How to identify rvalue and lvalue in an expression?
lvalue (from left-hand side (LHS) value) in something that refers to a memory (or register) storage and that you can assign values to. *p++
is an lvalue since it is a dereferenced pointer (i.e. refers to the location in memory that ptr
points to while the value of ptr
itself is the address of that location) and ++*ptr++
actually means: *ptr = *ptr + 1; ptr = ptr + 1;
- it increments the value pointed to by ptr
and then increments the pointer value itself. i++
is not an lvalue since it is the value of i
incremented by 1 and does not refer to a location in memory. You can think of such values as final - they cannot be further modified and can only be used as values to assign to lvalues. That's why they are called rvalues (from right-hand side (RHS) value).
LHS and RHS refer to both sides of the assignment expression A = B;
. A
is the LHS and B
is the RHS.
The term lvalue has been with C (and was carried forward to C++ and expanded later on). There was no rvalue to begin with. The draft I have (N1570) does list two occurrences of the term rvalue -- oncee in footnote #64 and once in the index.
In a nutshell: In the C world you have two types of objects -- lvalues and everything else.
Note that footnotes are not part of the standard but they can provide some helpful insights. Here goes footnote 64:
64) The name ‘‘lvalue’’ comes originally from the assignment expression E1 = E2, in which the left
operand E1 is required to be a (modifiable) lvalue. It is perhaps better considered as representing an
object ‘‘locator value’’. What is sometimes called ‘‘rvalue’’ is in this International Standard described
as the ‘‘value of an expression’’.
An obvious example of an lvalue is an identifier of an object. As a further example, if E is a unary
expression that is a pointer to an object, *E is an lvalue that designates the object to which E points.
This gives a good start. Now, bear in mind that an expression is built up from objects (and operators but we'll get there in a bit), and there are two fundamental things that you need to worry about when dealing with objects: Type and Value. Let's see what does the standard say about the type restrictions then (6.3.2.1/p1):
An lvalue is an expression (with an object type other than void) that potentially
designates an object;64) if an lvalue does not designate an object when it is evaluated, the behavior is undefined.
Also, note the next line which is important:
When an object is said to have a particular type, the type is
specified by the lvalue used to designate the object.
So, an lvalue can be used as a substitute for the type (we'll see this too). Next, let us take a look at the contexts where an object is an lvalue (6.3.2.1/2):
when it is the operand of the sizeof operator, the _Alignof operator, the
unary & operator, the ++ operator, the -- operator, or the left operand of the . operator
or an assignment operator
So, these are the operators that you need to keep an eye out for. In all other cases:
an lvalue that does not have array type is converted to the
value stored in the designated object (and is no longer an lvalue); this is called lvalue
conversion.
There are two special types: arrays and function designators. These decay i.e. are converted to an expression with type ‘‘pointer to type’’ that points
to the initial element of the array object and is not an lvalue / "pointer to function returning type". (Remember, we had paused on the fact that lvalues can work as types -- this is exactly what they do with sizeof
and _Alignof
!)
From Deitel and Deitel:
Variable names are said to be lvalues (for "left values") because they can be used on the left side of an assignment operator. Constants are said to be rvalues (for "right values") because they can be used on only the right side of an assignment operator. Note that lvalues can also be used as rvalues, but not vice versa.
x = 3; /* here, x is an lvalue */
c = x; /* and in the next line it is an rvalue */