r- r-value vs l-value?

K

Keith Thompson

SuperKoko said:
Zach said:
Chris said:
Sure. An l-value is an expression that may legally appear on the left-
hand side of the assignment operator, =. An r-value is an expression
that may appear on the right-hand side. The set of r-values is a subset
of the the set of l-values. For example, given:

int a;
int b[50];

The following are l-values (and also r-values):

a
b[10]

Whereas the following are ONLY r-values:

17
a + 5
b

Therefore, the following line of code will fail to compile:

a + 5 = 15;

Hope that helps,

Where are the rules of what may go on each side?
Since there is a lot of confusion in that discussion, I'll give rules
(based on C++).

How are rules based on C++ helpful in comp.lang.c?
First, I'll correct statements of Chris Smith:
int a;
int b[50];
a is an lvalue
b[10] is an lvalue
17 is an rvalue
a+5 is an rvalue
b *is an lvalue*

b is the name of an array object. As an expression of array type,
if it's not the operand of a unary "&" or "sizeof" operator, it is
implicitly converted to a pointer. C99 6.3.2.1p3:

Except when it is the operand of the sizeof operator or the unary
& operator, or is a string literal used to initialize an array, an
expression that has type "array of _type_" is converted to an
expression with type "pointer to _type_" that points to the
initial element of the array object and is not an lvalue.

Before this conversion takes place, b is an lvalue; after the
conversion, it isn't. Since there isn't really a "before" and
"after", I suppose you could think of it as a kind of Schroedinger's
lvalue. :cool:}

[...]
lvalues are not rvalues and rvalues are not lvalues... They are totally
different concepts.

The C standard doesn't use the term "rvalue" except in a single
footnote, which says:

What is sometimes called "rvalue" is in this International
Standard described as the "value of an expression".

[...]
If an lvalue-expression is found in a context where an rvalue is
expected, an rvalue-to-lvalue conversion occurs (and this conversion is
not conceptual... Physical things occur... And HERE can happen
segmentation faults).

I think you mean an lvalue-to-rvalue conversion. I suppose this
refers to fetching the value of the designated object.

[...]
Comma operator: It doesn't expect any particular type of value (i.e. it
accepts both lvalues and rvalues with different semantics). [...]
In C, AFAIK, the left operand can be either an rvalue or an lvalue and
the value is discarded (as in C++), but the right operand is expected
to be an rvalue, and the expression yields an rvalue.

In C, the operands are expressions. The left operand is evaluated and
its result is discarded. The right operand is then evaluated and its
result becomes the result of the operation. No need to talk about
"rvalues".

[...]
If you want to know all the C++ rules for conditional expressions, look
at:
http://www.open-std.org/jtc1/sc22/wg21/docs/wp/html/nov97-2/expr.html#expr.cond

And if you want to discuss those rules, I recommend comp.lang.c++.
 
O

Old Wolf

Keith said:
SuperKoko said:
int b[50];
b *is an lvalue*

b is the name of an array object. As an expression of array type,
if it's not the operand of a unary "&" or "sizeof" operator, it is
implicitly converted to a pointer. C99 6.3.2.1p3:

Except when it is the operand of the sizeof operator or the unary
& operator, or is a string literal used to initialize an array, an
expression that has type "array of _type_" is converted to an
expression with type "pointer to _type_" that points to the
initial element of the array object and is not an lvalue.

Before this conversion takes place, b is an lvalue; after the
conversion, it isn't. Since there isn't really a "before" and
"after", I suppose you could think of it as a kind of Schroedinger's
lvalue. :cool:}

I think that the conversion should not take place until b is being
"promoted" as an operand of another operator. (I don't think
the standard text supports this, but I think it should :)

With every other expression in C, you can determine its type
before seeing what context the expression is in. It would be a
"hack" to say that the expression

b

has a different type depending on which larger expression it
appears in. It seems cleaner to say that the above expression
has type (array[50] of int), but when it needs to be converted
to an rvalue (ie. for the purposes of being an operand of an
operator other than & etc.), it gets converted to a pointer.

I think the situation is analogous to:

3U + 3

we could say "3 has type int, except when being added to
an unsigned integer, in which case it has type unsigned int".
(and similar exceptions for the other operators and so forth).
But it is easier to state a rule in terms of promotion.

The case of:

b;

ie. where it is not an operand of anything, could be defined
either way; I don't see what difference it would make.
 
S

SuperKoko

muttaa said:
Mr.Chris, will you please explain how array names can be an l-value?
Like that:

char array[24];
char (*parray)[24]=&array; /* here, array is an lvalue, and its address
is taken*/
size_t i=sizeof(array); /* here, array is an lvalue and the size of the
array is equal to sizeof(char[24]) */


Otherwise, other expressions having array type get are automatically
converted to a pointer to their first element.

Keith Thompson quoted the standard:
C99 6.3.2.1p3:
Except when it is the operand of the sizeof operator or the unary
& operator, or is a string literal used to initialize an array, an
expression that has type "array of _type_" is converted to an
expression with type "pointer to _type_" that points to the
initial element of the array object and is not an lvalue.

C++ has rules which reduce the number of (lvalue or
rvalue)array-to-(rvalue)pointer conversions.
But actually, it doesn't create any real incompatibility.
 
J

Joe Wright

SuperKoko said:
muttaa said:
Mr.Chris, will you please explain how array names can be an l-value?
Like that:

char array[24];

Here, array designates a region of storage. Some (not I) call this a
'non-modifiable lvalue'. I submit that array cannot be an lvalue because
it cannot be assigned to.
char (*parray)[24]=&array; /* here, array is an lvalue, and its address
is taken*/

No. parray is the lvalue here. array appears as an rvalue.
size_t i=sizeof(array); /* here, array is an lvalue and the size of the
array is equal to sizeof(char[24]) */
No. array is not an lvalue in any of this.
Otherwise, other expressions having array type get are automatically
converted to a pointer to their first element.

Keith Thompson quoted the standard:

C++ has rules which reduce the number of (lvalue or
rvalue)array-to-(rvalue)pointer conversions.
But actually, it doesn't create any real incompatibility.
I won't deal with your C++ stuff. I do C here.

An lvalue is an expression referring to modifiable storage that neatly
fits on the left side of a simple assignment expression. The expression
array is not one of them.

Of course *(array+0) is an lvalue, as is array[0].
 
K

Keith Thompson

Joe Wright said:
An lvalue is an expression referring to modifiable storage that neatly
fits on the left side of a simple assignment expression. The
expression array is not one of them.

Did you consider checking the standard before posting this? Or
perhaps the rest of this thread?

The C standard, unfortunately, has a flawed definition of "lvalue",
but the intent is clear: an lvalue is an expression that designates,
or potentially designates, an object. The definition does not depend
on whether the object is modifiable. If it did, then C99 6.5.3.2
wouldn't make much sense:

The operand of the unary & operator shall be either a function
designator, the result of a [] or unary * operator, or an lvalue
that designates an object that is not a bit-field and is not
declared with the register storage-class specifier.

If "arr" is the name of a declared array object, then &arr is a legal
expression, precisely because arr is an lvalue.

If you want to discuss expressions that can legally appear as the left
operand of an assignment operator, you'll need to find another word or
phrase to use to refer to them (perhaps something like "modifiable
lvalues").
 
P

pete

Joe said:
char array[24];

Here, array designates a region of storage. Some (not I) call this a
'non-modifiable lvalue'.

Those who do,
are those who are not comfortable
making up their own definitions for technical terms in C.
I submit that array cannot be an lvalue because
it cannot be assigned to.
An lvalue is an expression referring to modifiable storage that neatly
fits on the left side of a simple assignment expression.

You, obviously,
*are* comfortable making up your own definitions
for what are technical terms in C.
 
C

Chris Smith

Keith Thompson said:
If you want to discuss expressions that can legally appear as the left
operand of an assignment operator, you'll need to find another word or
phrase to use to refer to them (perhaps something like "modifiable
lvalues").

This seems a bit unreasonable to me. The word l-value predates the C
language specification, and is used across numerous languages to mean
something specific: an expression that may occur on the left-hand side
of an assignment operator.

If the C language specification defines the term otherwise (as it
apparently does), then the logical response would be to interpret the
specification as if the word has the different meaning, but to also
acknowledge the existing uses as valid. This is a pretty universal part
of the human experience; just yesterday, I read a paper that began with
something to the effect of "in this paper, we will consider a sparse
graph to be one with O(n^(3/2)) edges", and yet I can continue to
discuss sparse graphs in the conventional O(n) sense when I'm not
reading that particular paper. I'd suggest the same thing is possible
with the C language specification.
 
K

Keith Thompson

Chris Smith said:
This seems a bit unreasonable to me. The word l-value predates the C
language specification, and is used across numerous languages to mean
something specific: an expression that may occur on the left-hand side
of an assignment operator.

Ok. I don't think I've seen the term "lvalue" used other than for C
and C++, but of course my experience is not universal.
If the C language specification defines the term otherwise (as it
apparently does), then the logical response would be to interpret the
specification as if the word has the different meaning, but to also
acknowledge the existing uses as valid. This is a pretty universal part
of the human experience; just yesterday, I read a paper that began with
something to the effect of "in this paper, we will consider a sparse
graph to be one with O(n^(3/2)) edges", and yet I can continue to
discuss sparse graphs in the conventional O(n) sense when I'm not
reading that particular paper. I'd suggest the same thing is possible
with the C language specification.

What's *not* reasonable is insisting that the term means some
particular thing while not acknowledging that the C standard defines
it differently. (See also "byte", "object", et al.)

I'd say that discussing C in this newsgroup is equivalent to "reading
that particular paper". It's difficult to discuss C if we don't agree
on the definitions of terms used by the C standard, even if common
usage might be different. Otherwise we might as well not have a
standard.
 
S

SuperKoko

Keith said:
Ok. I don't think I've seen the term "lvalue" used other than for C
and C++, but of course my experience is not universal.
It was also used for B and BCPL:
http://cm.bell-labs.com/cm/cs/who/dmr/kbman.html
http://cm.bell-labs.com/cm/cs/who/dmr/bcpl.pdf
However there was no problem such as non-modifiable lvalue, because all
lvalues could effectively appear on the left side of an assignment
operator.

It worth noticing that BCPL had a 'rv' and a 'lv' keyword (whose C
equivalent is & and *).
If you think about how to program a BCPL compiler, you'll see that
these two notions are very simple, and would appear in a compiler's
code.
Any expression yields a word (the only data type) ... And there are two
types of expressions:
Expressions yielding a word, and the value of the word is the value it
designates (and thus, for instance, adding 0 to this value doesn't
change this value). These expressions are named rvalues.
Expressions yielding a word designating a place in memory (i.e. a
reference) ... And to get the real value of the object, the compiler
has to dereference this word (and thus, adding 0 to this value yields a
rvalue which is the value designated-to by the lvalue). Such
expressions are called lvalue.

The rv operator interprets a lvalue word as if it were an rvalue.
The lv operator interprets an rvalue word as if it were an lvalue, and
if it is applied to an lvalue it first convert the lvalue to an rvalue
by accessing the memory designated by the lvalue (anyway, this
conversion occurs anywhere, when a lvalue is found in a place where an
rvalue is expected).

This C reference manual (1975):
http://cm.bell-labs.com/cm/cs/who/dmr/cman.pdf

Says that identifiers are lvalues... But also says that array
identifiers are not lvalues but rvalues equal to the pointer to their
first element. It even says "Because of this conversion, arrays are not
lvalues".
On another hand, it says that address-of operator can only be applied
on lvalue (hence it can't be applied on array identifiers).
It also says that the unary * operator yields an lvalue (but it doesn't
say that it yields an rvalue if the operand is of type
pointer-to-array).

Note that structs were not assignable and were lvalues... That's why
IMHO saying that an lvalue can always be put on the left side of an
assignment operator is not a good definition.
 
C

Chris Smith

Keith Thompson said:
I'd say that discussing C in this newsgroup is equivalent to "reading
that particular paper". It's difficult to discuss C if we don't agree
on the definitions of terms used by the C standard, even if common
usage might be different. Otherwise we might as well not have a
standard.

Okay. Perhaps I just misinterpreted your statement: "If you want to
discuss expressions that can legally appear as the left operand of an
assignment operator, you'll need to find another word or phrase to use
to refer to them". If you specifically meant in this newsgroup, then we
don't disagree.
 
K

Keith Thompson

Chris Smith said:
Okay. Perhaps I just misinterpreted your statement: "If you want to
discuss expressions that can legally appear as the left operand of an
assignment operator, you'll need to find another word or phrase to use
to refer to them". If you specifically meant in this newsgroup, then we
don't disagree.

Yes, I specifically meant in this newsgroup.
 
W

whyglinux

pete said:
If (b) was not an lvalue,
then (&b) would be undefined.

But that's not true.

For example, if (b) is a function, which is not an lvalue, or the
result of a [] or unary * operator, either an lvalue or not, (&b) is
still well-defined.
 
P

pete

whyglinux said:
But that's not true.

For example, if (b) is a function, which is not an lvalue,

You're right about functions.
I meant if (b) was an rvalue instead of an lvalue.
or the result of a []

Any result of a [], is an lvalue.
 
W

whyglinux

pete said:
Any result of a [], is an lvalue.

If you were right, then

6.5.3.2 Address and indirection operators, paragraph 1:
"The operand of the unary & operator shall be either a function
designator, the result of a [] or unary * operator, or an lvalue that
designates an object that is not a bit-field and isnot declared with
the register storage-class specifier."

would had been written like this:

The operand of the unary & operator shall be either a function
designator or an lvalue that designates an object that is not a
bit-field and is not declared with the register storage-class
specifier.

But this is not true. Therefore, although "the result of a [] or unary
* operator" is usually an lvalue, but must not be only an lvalue here.
The difference between "the result of a [] or unary * operator" and an
lvalue is that "the result" can be either an lvalue or rvalue.

Not only a function or a qualified lvalue, but also an rvalue, only if
it is "the result of a [] or unary * operator", can be taken as the
operand of the unary & operator. Rvalue operand is a case that needs
to be listed specially.
 
P

pete

whyglinux said:
Any result of a [], is an lvalue.

If you were right, then

6.5.3.2 Address and indirection operators, paragraph 1:
"The operand of the unary & operator shall be either a function
designator, the result of a [] or unary * operator, or an lvalue that
designates an object that is not a bit-field and isnot declared with
the register storage-class specifier."

would had been written like this:

The operand of the unary & operator shall be either a function
designator or an lvalue that designates an object that is not a
bit-field and is not declared with the register storage-class
specifier.

There's no evidence that that is true.
But this is not true. Therefore, although "the result of a [] or unary
* operator" is usually an lvalue, but must not be only an lvalue here.

Your conclusion is wrong.
The result of a [] has an address.
[] is a binary operator,
with a being equal to *((a) + (b)).
The address of a is ((a) + (b)).
Everything that has an address in a C program,
is either a function or an object.
Function pointers aren't allowed as operands
of the addition operator, so the pointer part of a,
must be an object pointer, and a must be an object.

If you were right, then you could give an example
where the result of the [] operator, is not an lvalue.
 
W

whyglinux

pete said:
whyglinux said:
Any result of a [], is an lvalue.

If you were right, then

6.5.3.2 Address and indirection operators, paragraph 1:
"The operand of the unary & operator shall be either a function
designator, the result of a [] or unary * operator, or an lvalue that
designates an object that is not a bit-field and isnot declared with
the register storage-class specifier."

would had been written like this:

The operand of the unary & operator shall be either a function
designator or an lvalue that designates an object that is not a
bit-field and is not declared with the register storage-class
specifier.

There's no evidence that that is true.
But this is not true. Therefore, although "the result of a [] or unary
* operator" is usually an lvalue, but must not be only an lvalue here.

Your conclusion is wrong.
The result of a [] has an address.
[] is a binary operator,
with a being equal to *((a) + (b)).
The address of a is ((a) + (b)).
Everything that has an address in a C program,
is either a function or an object.
Function pointers aren't allowed as operands
of the addition operator, so the pointer part of a,
must be an object pointer, and a must be an object.

If you were right, then you could give an example
where the result of the [] operator, is not an lvalue.


It's easy. The following is an example:

typedef struct A {
int x[10];
} A;

A func() {
A a;
return a;
}

int main( void )
{
int* p0 = &(func().x[0]);
/*This is valid though func().x[0] is an rvalue object */
/* Note: for C99 only.
C89 does not permit to take an rvalue's address in any case. */

/* &(func().x) is invalid because func().x is an rvalue array, and is
neither the result of a [] operator nor that of a * operator. */

return 0;
}

In fact, not all objects are lvalues (for example, the returned objects
by functions). The Standard should point this out but fails to do so,
perhaps due to the fact that taking an rvalue object's address is rare
and seems useless in C.
 
P

pete

whyglinux said:
pete wrote:
If you were right, then you could give an example
where the result of the [] operator, is not an lvalue.

It's easy. The following is an example:

typedef struct A {
int x[10];
} A;

A func() {
A a;
return a;
}

int main( void )
{
int* p0 = &(func().x[0]);
/*This is valid though func().x[0] is an rvalue object */
/* Note: for C99 only.
C89 does not permit to take an rvalue's address in any case. */

I tend to have a C89 bias,
which I now admit means that I may be wrong on this topic.
 
C

Chris Torek

typedef struct A {
int x[10];
} A;

A func() {
A a;
return a;
}

int main( void )
{
int* p0 = &(func().x[0]);
/*This is valid though func().x[0] is an rvalue object */
/* Note: for C99 only.
C89 does not permit to take an rvalue's address in any case. */

/* &(func().x) is invalid because func().x is an rvalue array, and is
neither the result of a [] operator nor that of a * operator. */

return 0;
}

In fact, not all objects are lvalues (for example, the returned objects
by functions). The Standard should point this out but fails to do so,
perhaps due to the fact that taking an rvalue object's address is rare
and seems useless in C.

Indeed, this is a peculiar situation, and one I think at best poorly
covered in both C89 and C99. Even if you can do the above with "p0",
you can never *use* p0 in any way after that.

It probably would have been better (in my opinion at least) if both
standards said something like this:

When a function returns a structure value, this produces a
value-context object. The only defined operations on a
value-context object are:

- to discard it entirely, or
- to copy it wholly, as by assignment to an ordinary
object or parameter, or
- to take a pointer to it whose value is not used beyond the
next sequence point, or
- to select an element (via the "." operator), producing a
value-context object to which these rules apply recursively.

For example, if f() has a structure return value with elements
x and y, and x has array type and y has type int:

struct S { int x[N]; int y; /* and possibly others */ };
struct S f(void);
struct S r;
void g(int);

(void) f(); /* valid (discarded entirely) */
r = f(); /* valid (copied wholly) */
g(f().y); /* valid (element selected then copied wholly) */

memcpy(r.x, f().x, sizeof r.x); /* invalid -- f().x is
not being "copied wholly"; instead, a pointer is taken
and used beyond the next sequence point */
memcpy(&r.x[0], &f().x[0], sizeof r.x); /* still invalid,
for the same reason */

r.x[0] = f().x[0]; /* valid (albeit complicated!) */

Note that these rules also permit one to write, e.g.:

g((&(f()))->y);

I have fully parenthesized this to show where the operators bind:
f() returns a "struct S"; we take the address of this "value context
object" -- or "rvalue object" if you prefer such terms -- but
immediately, without an intervening sequence point, use the "->"
operator to select an element whose value is then copied wholly,
to be sent as a parameter value to g().

The above rules probably need some additional clarification (probably
grouped with the unary "*" operator) so that it is clear that "*",
applied to one of these funny pointers resulting from taking the
address of a "value-context object", produces the actual value-
context object in question, to which the special rules apply.

All of this leaves room for a future C standard to define operations
on entire arrays, so that one can write:

r.x = f().x;

to copy the whole "x" array without copying the "y" value. And, in
any case, the goal is to make the lifetime of a "value-context
object" that of a single between-sequence-points region, as if
there were a brace-enclosed local variable involved, whose lifetime
is only that of the expression:

int h(int *);

h(f().x); /* invalid */

is treated much as if we had written something like:

int h(int *);
int *p;
{
struct S temp;
temp = f();
p = &temp.x[0];
/* note that uses of both p and *p are valid here ... */
}
/* but uses of p or *p are undefined here */
h(p);

This mirrors how compilers are often likely to treat structure-valued
functions: they "return" an object whose lifetime is that of a stack
temporary, created just long enough to run the expression(s) involved,
and the way the value is "returned" is via a secret parameter. That
is:

g(f().y);

is compiled as something very much like:

{ struct S temp; f(&temp); g(temp.y); }

in a number of actual implementations. I only say "very much like"
because the temp.x space may be re-used or discarded even "inside
the braces" here, e.g., assembly code like:

sub #44,sp /* make stack space for "temp" */
lea 0(sp),r1
call f /* f(&temp) */
add #40,sp /* delete temp.x but leave temp.y */
mov (sp)+,r1 /* fetch temp.y and finish deleting temp */
call g /* g(that) */

(on a machine with a stack, and arguments passed in registers r1
onward).
 
B

Ben C

typedef struct A {
int x[10];
} A;

A func() {
A a;
return a;
}

int main( void )
{
int* p0 = &(func().x[0]);
/*This is valid though func().x[0] is an rvalue object */
/* Note: for C99 only.
C89 does not permit to take an rvalue's address in any case. */

/* &(func().x) is invalid because func().x is an rvalue array, and is
neither the result of a [] operator nor that of a * operator. */

return 0;
}

In fact, not all objects are lvalues (for example, the returned objects
by functions). The Standard should point this out but fails to do so,
perhaps due to the fact that taking an rvalue object's address is rare
and seems useless in C.

Indeed, this is a peculiar situation, and one I think at best poorly
covered in both C89 and C99. Even if you can do the above with "p0",
you can never *use* p0 in any way after that.
[...]

I just think life would have been simpler if you couldn't return
structure instances at all-- after all you can't return arrays. I don't
know if this ability to return struct instances was in the earliest
versions of C or if it was added later.

Practically speaking it's quite a convenient feature and not often a
cause of real-world problems I suppose. But functions always return
values, and objects are (everywhere else) not values, but places you can
store values. Allowing objects to be returned from functions introduces
an inconsistency.
 
I

Ian Collins

Ben said:
I just think life would have been simpler if you couldn't return
structure instances at all-- after all you can't return arrays. I don't
know if this ability to return struct instances was in the earliest
versions of C or if it was added later.
Arrays don't have copy or assignment semantics, structures do. To
return something by value, you have to copy it. So it does make sense
to be able to return a structure and not an array.
Practically speaking it's quite a convenient feature and not often a
cause of real-world problems I suppose. But functions always return
values, and objects are (everywhere else) not values, but places you can
store values. Allowing objects to be returned from functions introduces
an inconsistency.

But you can assign to one, so it is consistent to be able to return one.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,197
Latest member
Sean29G025

Latest Threads

Top