gcc: pointer to array

P

pete

Netocrat wrote:
After it decays into int*, it's no longer an lvalue.

What would stop it from being an lvalue? It fits the definition of
"[a]n expression with an object type ... other than void".

"A cast does not yield an lvalue"

The result of a type conversion, can't be an lvalue.

char object;

(int)object = INT_MAX; /* no good */

Only one byte of memory is reserved for object.
There isn't really an int type object there.
 
P

pete

Netocrat wrote:
OK I agree that it should be explicit.
How about this for an alternate
definition of an lvalue:
"Any expression which after removing any possible
register storage-specifier, we can take the address of."

Function identifiers aren't lvalues either,
even though functions have addresses.
 
P

pete

pete said:
After it decays into int*, it's no longer an lvalue.

What would stop it from being an lvalue? It fits the definition of
"[a]n expression with an object type ... other than void".

N869

6.3.2 Other operands
6.3.2.1 Lvalues and function designators

[#3] 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.
 
S

S.Tobias

How about this for an alternate
definition of an lvalue: "Any expression which after removing any possible
register storage-specifier, we can take the address of."

Perhaps it's close, I don't know. You have to define what it means
"to take the address of" - if it's applying "&" operator, then
how do you define what can be operand to it - something that
"&" can be applied to? As pete indicated, function types
must be taken care of; also void expressions and bit fields.

struct s { int a[1]; };
struct s f(void);

f().a[0] = 7; //UB

The expression on the left is clearly a modifiable lvalue.

This is a minor variation on an example from a previous thread in
c.l.c:
struct s {...} s1, s2;
(s1 = s2).a[0] = 7; //UB

I remember reading that thread - "Undefined behaviour when modifying
the result of an assignment operator".

Not so clearly according to gcc. I get: "ISO C90 forbids
subscripting non-lvalue array".

gcc is lying. The expression "f().a[0]" is perfectly valid.

Well... perhaps not then.
It's perfectly valid as a non-modifiable lvalue; but we both agree that
assigning to it as a modifiable lvalue invokes undefined behaviour.

Try it with on-line como - there's not a single warnings. Cut'n'paste
version:
int main()
{
struct s { int a[1]; };
struct s f(void);
f().a[0] = 7; /*UB*/
}

Similarly as for gcc, we don't get warnings in C99 mode, but in C90
mode on-line como gives:

"ComeauTest.c", line 5: error: invalid use of non-lvalue array
f().a[0] = 7; /*UB*/
^

Yes, thanks, I didn't test all possible options. I didn't find
any significant changes in constraints to "[]" and "*", and semantics
of "." operators. Perhaps the difference between C90 and C99
results only from the change in the definition and interpretation
of an lvalue.

Also I have noticed the parts "If an attempt is made to modify
the result of [...] or to access it after the next sequence point,
the behavior is undefined." referring to assignments and function
calls are not there in C90.
[snip]
int a[1];
a[0];
`a' is not a modifiable lvalue, `a[0]' is (cf. Rationale).

Agreed, but I was talking about f(), not f().a. In the case of f(), it
does have to be. If the struct returned by f() is not a modifiable
lvalue, then neither are the elements of its member array.

It doesn't matter, really.

It does though. You ignored my reasoning. f() returns a struct. If
that struct in this context is not a modifiable lvalue, then its elements
aren't modifiable lvalues either. This applies to the elements of its
array member.
`a' is not an lvalue, but `a[0]' is.
ITYM "modifiable lvalue". But yes, in the generic case where a is a
non-const array, I agree.
Thanks, it was late.
Again, ITYM that it is an lvalue but not a modifiable lvalue.
"Similarly" is not the right word here, but since `f()' is not
an lvalue, neither is `f().a' (before conversion to pointer).
(By accident I was actually right.) This is a cv:
&f().a;

`f().a[0]' is the same as
`*(f().a + 0)', dereferencing a pointer is always an lvalue (in this
case it is even well defined by the Std, because `f().a' presumably
points to some valid temporary storage). Since its type is `int', it's
a modifiable lvalue.

Your reasoning is sound but predicated on the basis that f() itself is a
modifiable lvalue.

No, `f()' is not even an lvalue, and neither is `f().a'.
But `f().a[0]' is.
It falls apart when that is not the case... ie
consider const struct s cs. Now cs.a[0] is not a modifiable lvalue
because cs is not. The same reasoning applies to f().

`cs.a[0]' is not modifiable because members inherit qualifiers
(here: `const') from their containing types. It is not modifiable
not because `cs' isn't, but because its type is `const int'.
This is a modifiable lvalue:
((int*)cs.a)[0] //UB if modified

For another example:
struct s { int i; const int ci; } s;
`s' is a non-modifiable lvalue, but `s.i' is modifiable.
 
S

S.Tobias

Netocrat said:
As I've noted elsewhere, you are correct for C99 but in C90 *ps is
evaluated - at least conceptually i.e. this is a constraint violation:

int *n = NULL.
int *p;
p = &*n;

I don't think so. In C90 6.2.2.1 there's a paragraph which is equivalent
of 6.3.2.1#2 in C99 (in n869.txt) (they seem the same, but I haven't
checked). It means that at least the value of `*n' is not taken (because
it is operand to "&"). I don't see any cv in C90 except that `*n' is
not strictly an lvalue, which cannot (in general) be established at
compile time (due to poor definition of an lvalue).

In C99 in 6.5.3.2#3 it says that in "&*n", "*" is not evaluated. What
I think it means (and why it's necessary) is this: To evaluate "*"
is to establish lvalue for the object that `n' points to (it doesn't yet
mean to take a value - it wouldn't occur anyway in presence of "&").
It is UB if an lvalue doesn't designate an object, and it would
be UB if "*" were evaluated in this case; since it isn't, "&*n"
is well defined for all values of `n'.

But seriously, clearly *ps on its own is nonsensical; surely you agree
with me on that?

I agree. But OTOH the notion of an lvalue is more of a pointer, or
half-dereferenced pointer (which establishes only a position of an
object, but not its range or value). In this context "& * ps" is
not strange.
Apart from that, an lvalue is rather a tag in the grammatical
description of a language, that is, certain expressions are labeled
"lvalues", whether there is an object or not.
What does your compiler say to the simple statement
*ps; (gcc tells me where to get off)? That I haven't yet found a specific
prohibition in the standard merely convinces me that I don't know where to
look.

I haven't found either. I'm prepraring a question on that, too.
I'm not satisfied that your code is an example of an incomplete struct
occurring as an lvalue. In the case where &* is being treated as a no-op,
then semantically *ps (and hence the incomplete struct) never occurs.

But you have to know in advance if "*ps" is an lvalue before you
can put "&" and "*ps" together (whether "&*" is a no-op, or not);
if it weren't an lvalue, then there'd be constraint violation.

And in C99 "&*" is not quite a no-op: the expression after that
is no longer an lvalue (if it was before):
n = 0;
&*n = 0; //CV
 
S

S.Tobias

Peter Nilsson said:
S.Tobias said:
In comp.lang.c Peter Nilsson said:
S.Tobias wrote:

char *foo();

void bah(const char *x)
{
char *y = foo(x);

You raise UB here: (const char*) and (char*) types are
not compatible - you cannot pass incompatible arguments
to a function call (cf. 6.5.2.2#6).

C99 has different wording from N869.

[It looks like it's not valid C90 though.]

I'm not sure what you mean. Here's the quote from the C99 Standard
I was referring to:
(6.5.2.2p6)
# If the expression that denotes the called function has a type that
# does not include a prototype, [...]
# [...] If the function is defined with a type that includes a
# prototype, and either the prototype ends with an ellipsis (, ...)
# or the types of the arguments after promotion are not compatible
# with the types of the parameters, the behavior is undefined. [...]

At the time of calling, the function (foo) isn't defined with a
prototype. So the behaviour is determined by the '...' you snipped.

(I snipped only irrelevant parts.)

At the point of call, only the declaration "char *foo()" is visible,
therefore at that point the expression "foo" has a function type
without a prototype. 6.5.2.2p6 is applicable.

The function is defined (elsewhere, no matter where, might be in
a different translation unit) with a type that includes a prototype:
char *foo(char *x) {/*...*/}
That's where the next quoted part applies.

I don't see any ambiguity here.


If the function were defined (again: anywhere) with a type
without a prototype, ie:
char *foo(x) char *x; {/*...*/}
then further (snipped) part would apply.

However, I think the second "dash" exception is ambiguous
(in how "both" and the two "or"s should be interpreted):
const char *pc;
foo(pc); //UB?

[Warning: there're differences between n869 and the Std there.]
 
N

Netocrat

pete said:
After it decays into int*, it's no longer an lvalue.

What would stop it from being an lvalue? It fits the definition of
"[a]n expression with an object type ... other than void".

N869

6.3.2 Other operands
6.3.2.1 Lvalues and function designators

[#3] 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.

Which you seem to be justifying using the logic of your previous post:
"A cast does not yield an lvalue"

The result of a type conversion, can't be an lvalue.

This falsely gives equivalence to the terms "cast" and "type conversion".
See the wording of N869, 6.3#1 for evidence that this is false. The
conversion by which an array decays into a pointer is not a cast and not
subject to such rules.

As further proof consider that if the pointer to which the array decays
were not an lvalue, we would be prohibited from assigning to its elements.
char object;

(int)object = INT_MAX; /* no good */

Only one byte of memory is reserved for object. There isn't really an
int type object there.

Agreed that (int)object is not an lvalue, but its non-modifiability is
not the proof.
 
N

Netocrat

I don't think so.

Perhaps if you read the defect reports that I referenced you would. Those
reports are #012, #076 and #106, the one dealing specifically with this
case being #076.

Note the final words of #012, dealing with a related case of void *p;
"...as long as a diagnostic message is issued, a translator may assign a
meaning to the expression &*p discussed above. Conforming programs shall
not use this expression, however."

Pretty explicit.
In C90 6.2.2.1 there's a paragraph which is equivalent
of 6.3.2.1#2 in C99 (in n869.txt) (they seem the same, but I haven't
checked). It means that at least the value of `*n' is not taken
(because it is operand to "&").

I only have access to n869 and the C89 draft. n869 has no 6.2.2.1 and
the C89 draft has different numbering, so I don't know where to find that
paragraph. Quote it if you can.

Given the defect reports though, the behaviour required under C90 is clear.
I don't see any cv in C90 except that `*n' is
not strictly an lvalue, which cannot (in general) be established at
compile time (due to poor definition of an lvalue).

That's significant enough to prevent the program from being conforming, so
why downplay it?

Defect report #076:
"Subclause 6.3.3.2 requires the operand of &to be an lvalue; NULL is
not an lvalue. [...] [Therefore t]he use of [the construct *n where n ==
NULL] prevents a program from being strictly conforming..."
In C99 in 6.5.3.2#3 it says that in "&*n", "*" is not evaluated. What I
think it means (and why it's necessary) is this: To evaluate "*" is to
establish lvalue for the object that `n' points to (it doesn't yet mean
to take a value - it wouldn't occur anyway in presence of "&").

How could evaluating "*" - in the case where "&*" is not a no-op -
not result in taking a value?

Perhaps you would accept this as a clarification/expansion: at compile
time it is established whether the "*" operator results in an lvalue, but
it cannot always be established that it is legal to evaluate that lvalue.
At runtime evaluating that lvalue may in some cases be illegal.

Also the definition of the indirection operator is lacking in the case of
pointer to incomplete struct.

N869, 6.5.3.2#4:
The unary * operator denotes indirection. If the
operand points to a function, the result is a function
designator; if it points to an object, the result is an
lvalue designating the object. If the operand has type
``pointer to type'', the result has type ``type''. If an
invalid value has been assigned to the pointer, the behavior
of the unary * operator is undefined.74)

In this case, the operand points to neither a function nor an object,
since an incomplete type cannot be an object (as implied by the definition
of lvalue). So all that is specified in this case is the type. This is
where I would have expected to find wording to the effect of "if it points
to an incomplete type, the result is that a syntax error occurs except in
cases where the & operator is immediately applied".
It is UB
if an lvalue doesn't designate an object, and it would be UB if "*" were
evaluated in this case;
Agreed.

since it isn't, "&*n" is well defined for all
values of `n'.

Only in C99 - this is not true in C90 as the defect reports clarify.
I agree. But OTOH the notion of an lvalue is more of a pointer, or
half-dereferenced pointer (which establishes only a position of an
object, but not its range or value). In this context "& * ps" is not
strange.

It's not strange, but without the &* no-op, it's invalid. In 6.3.2.1 as
quoted top of post: "if an lvalue does not designate an object when it is
evaluated, the behavior is undefined." Since *ps represents an incomplete
type, it cannot be an object. Therefore the behaviour is undefined.

So 6.3.2.1 is effectively saying, "an expression with incomplete type is
an lvalue, but cannot be evaluated as one". Which is pretty meaningless
except for the &* case.

But you have to know in advance if "*ps" is an lvalue before you can put
"&" and "*ps" together (whether "&*" is a no-op, or not); if it weren't
an lvalue, then there'd be constraint violation.

So we could say that *ps is known in advance to be an lvalue but its
evaluation as such is prohibited.
And in C99 "&*" is not quite a no-op: the expression after that is no
longer an lvalue (if it was before):
n = 0;
&*n = 0; //CV

Agreed, I missed that.
 
P

pete

Netocrat said:
pete said:
Netocrat wrote:

After it decays into int*, it's no longer an lvalue.

What would stop it from being an lvalue?
It fits the definition of
"[a]n expression with an object type ... other than void".

N869

6.3.2 Other operands
6.3.2.1 Lvalues and function designators

[#3] 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.

Which you seem to be justifying using the logic of your previous post:

No.
I'm quoting the standard for the purpose
of correctly answering your question:
"What would stop it from being an lvalue?"

The "and is not an lvalue." is quoted from the standard.
I separated it from the rest of the text to make it stand out.

This falsely gives equivalence to the
terms "cast" and "type conversion".

Yes.
I should have supplied the verse from the standard originally.
 
N

Netocrat

On Sun, 17 Jul 2005 00:14:39 +0000, pete wrote:

I'm quoting the standard for the purpose
of correctly answering your question:
"What would stop
[an array decayed to a pointer]
from being an lvalue?"

The "and is not an lvalue." is quoted from the standard. I separated
it from the rest of the text to make it stand out.

OK, you and Stan are correct.
 
P

Peter Nilsson

S.Tobias said:
Peter Nilsson said:
S.Tobias said:
S.Tobias wrote:

char *foo();

void bah(const char *x)
{
char *y = foo(x);

You raise UB here: (const char*) and (char*) types are
not compatible - you cannot pass incompatible arguments
to a function call (cf. 6.5.2.2#6).

C99 has different wording from N869.

[It looks like it's not valid C90 though.]

I'm not sure what you mean. Here's the quote from the C99 Standard
I was referring to:
(6.5.2.2p6)
# If the expression that denotes the called function has a type that
# does not include a prototype, [...]
# [...] If the function is defined with a type that includes a
# prototype, and either the prototype ends with an ellipsis (, ...)
# or the types of the arguments after promotion are not compatible
# with the types of the parameters, the behavior is undefined. [...]

At the time of calling, the function (foo) isn't defined with a
prototype. So the behaviour is determined by the '...' you snipped.

(I snipped only irrelevant parts.)

At the point of call, only the declaration "char *foo()" is visible,
therefore at that point the expression "foo" has a function type
without a prototype. 6.5.2.2p6 is applicable.

The function is defined (elsewhere, no matter where, might be in
a different translation unit) with a type that includes a prototype:
char *foo(char *x) {/*...*/}
That's where the next quoted part applies.

I don't see any ambiguity here.

Indeed, you are correct. I had it in my mind that C99 removed K&R style
function definitions.

Thank you for the correction.
 
N

Netocrat

Perhaps it's close, I don't know. You have to define what it means
"to take the address of" - if it's applying "&" operator, then
how do you define what can be operand to it - something that
"&" can be applied to?

Yes, "an expression that we can take the address of" would equate to "an
expression that we can legally apply the & operator to", although the
current definition of & relies on the definition of lvalue so this
dependency would have to be reversed or we have infinite recursion.

The current definition is more descriptive, but this alternative
definition works better for me. YMMV.

On first reading 6.3.2.1#1 it's not clear that there are qualifications
and messiness elsewhere. e.g. there is a separate footnote for each of the
cases of a comma operator, a cast, and a conditional expression; each
footnote specifying that the case in question does "not yield an lvalue".
Another example: the result of a function call is not an lvalue but this
isn't made explicit - the word "lvalue" does not occur in 6.5.2.2 and a
function return doesn't seem to violate the current lvalue definition of
"an expression with an object type or an incomplete type other than void".
It would seem to fit into the same category as a cast which is explicitly
defined as a non-lvalue.

Using this definition I don't have to consider such issues as part of the
lvalue definition - I can defer them to the & operator. Likewise I can
define the & operator in terms of lvalue and defer them back to the lvalue
definition. So I get to have my cake and eat it too. :)
As pete indicated, function types
must be taken care of; also void expressions and bit fields.

Hmm, a void expression can't occur as an lvalue, and we can reword for the
others:

"Any expression, apart from a function, which after removing any possible
register storage-specifier and treating a bit-field type as though it were
an ordinary structure member, we may legally take the address of."

int main()
{
struct s { int a[1]; };
struct s f(void);
f().a[0] = 7; /*UB*/
}

Similarly as for gcc, we don't get warnings in C99 mode, but in C90
mode on-line como gives:

"ComeauTest.c", line 5: error: invalid use of non-lvalue array
f().a[0] = 7; /*UB*/
^
Yes, thanks, I didn't test all possible options. I didn't find any
significant changes in constraints to "[]" and "*", and semantics of "."
operators. Perhaps the difference between C90 and C99 results only from
the change in the definition and interpretation of an lvalue.

That's it. The wording of 6.3.2.1 has changed from: "lvalue that has
value" to "expression that has value", relaxing the requirement for an
array to be an lvalue before it will decay into the pointer used to access
its elements.

int a[1];
a[0];
`a' is not a modifiable lvalue, `a[0]' is (cf. Rationale).

Agreed, but I was talking about f(), not f().a. In the case of f(),
it does have to be. If the struct returned by f() is not a
modifiable lvalue, then neither are the elements of its member array.

It doesn't matter, really.
`a' is not an lvalue, but `a[0]' is.
ITYM "modifiable lvalue". But yes, in the generic case where a is a
non-const array, I agree.
Thanks, it was late.
Again, ITYM that it is an lvalue but not a modifiable lvalue.
"Similarly" is not the right word here, but since `f()' is not an
lvalue, neither is `f().a' (before conversion to pointer). (By accident
I was actually right.) This is a cv:
&f().a;

So in this case we can validly claim that if the parent struct is not a
modifiable lvalue then neither is the member. However I accept your
reasoning that it doesn't extend to the array elements - you are right
that they are modifiable lvalues (invoking UB on modification) even though
f() and f().a are not.

<snip rest>
 
J

Joe Wright

pete said:
Netocrat wrote:




This is Lawrence Kirby's comments and corrections to my last attempt:

http://groups-beta.google.com/group/comp.lang.c/msg/27ff959caf56f520?hl=en&

Have you yet noticed that a constant expression like (5),
fits the C99 definition of an Lvalue which does not refer
to an object and causes undefined behavior when evaluated?
It is clear that 5 cannot be an lvalue and that the Standard is never
wrong. That puts our ability to 'read for comprehension' in question. :)
 
N

Netocrat

This is Lawrence Kirby's comments and corrections to my last attempt:

http://groups-beta.google.com/group/comp.lang.c/msg/27ff959caf56f520?hl=en&

Your definition with corrections seems pretty complete except for
incomplete types.
Have you yet noticed that a constant expression like (5),
fits the C99 definition of an Lvalue which does not refer
to an object and causes undefined behavior when evaluated?

That has to be a test question, right?

Going by N869 as I don't have the standard:

You claim that the C99 definition of an lvalue "does not refer to
an object", when it plainly does.

You also claim that a constant expression like (5) fits the C99 definition
of an lvalue, which it plainly does not. This constant expression is
neither an incomplete type, nor an object which the draft defines at
3.15#1 as a "region of data storage in the execution environment, the
contents of which can represent values" (the constant expression (5)
conceptually is not stored).

Finally your question implies that there is some sense in which evaluating
a constant as an lvalue leads to undefined behaviour, which again is
plainly false since a constant can never be evaluated as an lvalue.
Any attempt to treat it as a modifiable lvalue by assigning to it will be
caught as a syntax error, and will not cause undefined behaviour.

Did I pass?

The constant expression (5) contrasts with Lawrence Kirby's example of a
string literal, which must have data storage. His example also provides a
good test for my definition which holds up - it is valid to take the
address of a string literal.

In the thread you reference, Tim Rentsch comments:
Sadly there doesn't seem to be a simple means of
identifying lvalues the way assignment does for modifiable
lvalues.

With qualifications, a simple means of identifying an lvalue is to check
whether the address-of operator can be applied to the expression.

The qualifications are that you must exclude functions, remove register
storage-specifiers and treat bit-fields as though they were addressable
like other struct members are. I don't know whether that's simple enough.
 
P

pete

Netocrat said:
That has to be a test question, right?

Going by N869 as I don't have the standard:

You claim that the C99 definition of an lvalue "does not refer to
an object", when it plainly does.

No.
I claim that 5 is an lvalue.
I claim that 5 does not represent an object.

You also claim that a constant expression like
(5) fits the C99 definition
of an lvalue, which it plainly does not.
This constant expression is
neither an incomplete type, nor an object which the draft defines at
3.15#1 as a "region of data storage in the execution environment, the
contents of which can represent values" (the constant expression (5)
conceptually is not stored).

It makes no difference that 5 is not an object.
The definition of lvalue only requires an expression of object type.
5 is an expression of type int.
int is an object type.
There's 3 kinds of types:
object types, incomplete types, and function types.
5 is an expression with an object type, therefore 5 is an lvalue.
5 does not represent an object.
Evaluating 5 causes undefined behavior.

N869
6.3.2 Other operands
6.3.2.1 Lvalues and function designators
[#1] An lvalue is an expression with an object type or an
incomplete type other than void; if an lvalue does not
designate an object when it is evaluated, the behavior is
undefined.
Did I pass?

No. You confused "object" with "object type".
Even though object types describe objects,
constant expressions also have object type.

5
5u
5.0
5.0f
 
T

Tim Rentsch

Netocrat said:
That has to be a test question, right?

Going by N869 as I don't have the standard:

Quoting ISO/IEC 9899:1999(E) 6.3.2.1 p1:

An lvalue is an expression with an object type or an
incomplete type other than void;

This definition is hopelessly broken. Almost every expression is
an lvalue, including '(3+4)', '&i' (after 'int i;'), and even 'f'
(after 'int f(void);'), because a function type is converted to
pointer-to-function, which is an object type.

Other than expressions that happen to be void, about the only
expressions that aren't lvalues are things like the 'f' in
'sizeof f' or '&f' (but '&f' is still an lvalue). I realize this
isn't how CLC'ers are used to thinking of what "lvalue" means,
but if one looks at what the document actually says, essentially
all expressions are lvalues.

Furthermore, the definition excludes expressions that are
usefully considered with similar expressional forms:

char *p; struct foo *s; void *v;
*p; /* this is an lvalue */
*s; /* so is this, even when 'struct foo' is an incomplete type */
*v; /* this isn't an lvalue */

Clearly there are two distinct notions here: a syntactic notion
defining expressions that are (possible) candidates for
modification, and a semantic notion defining expressions that
refer to values but are not themselves values (ie, expressions
that designate objects). The combination of the first notion and
the second notion together with additional constraints such as
non-const-ness, etc, produce what we're used to calling a
<i>modifiable lvalue</i>.

I expect we're stuck with the term "lvalue" for the semantic
notion of designating an object, probably better termed "object
locator" or something similar. It would be an improvement to
give terms and defintions for both notions. What's most
important is that the definition for the object locator property
needs to be redone.

In the thread you reference, Tim Rentsch comments:


With qualifications, a simple means of identifying an lvalue is to check
whether the address-of operator can be applied to the expression.

The qualifications are that you must exclude functions, remove register
storage-specifiers and treat bit-fields as though they were addressable
like other struct members are. I don't know whether that's simple enough.

A good operational definition for modifiable lvalue is "can it be
written on the left hand side of an assignment operator"?

It seems rather useless (not counting having discussions in CLC)
to have a simple means to identify lvalues, because (1) the
definition is broken, and (2) what's usually needed is a test
that answers "is it legal to write thus and such an expression in
thus and such context", which is to say a syntactic constraint.
Most C programmers don't need to understand the term "lvalue"
as it is used in the standard.
 
N

Netocrat


OK, I misinterpreted that bit. You meant (5) rather than the C99
definition of an lvalue.
I claim that 5 is an lvalue.
I claim that 5 does not represent an object.

Agreed that your second claim is true.

The intent of the standard is that the above two claims cannot both be
true (except in the case of indirection, where it's not always clear at
compile-time whether an expression will evaluate to a valid object or not,
and in the case where 5 represents an incomplete type, which we agree it
does not).

Look at 6.3.2.1#2 where it refers to "the value stored in the designated
object", clearly indicating as I said that it is intended that only
expressions that designate an actual object (or can potentially designate
in the case of indirection) are to be considered lvalues.

Therefore the first claim is untrue.

This accords with my intuitive definition - it's illegal to take the
address of a constant.

Evaluating 5 causes undefined behavior.

6.5.16#2
An assignment operator shall have a modifiable lvalue
as its left operand.

Since 5 is not even an lvalue, let alone a modifiable lvalue, this
paragraph applies and attempting to assign to 5 is a constraint
violation - it will be rejected by a conforming compiler at compile time.
And there is nothing undefined about evaluating 5 in any other context -
it evaluates to "type int with value 5" according to 6.4.4.1#5.

I resubmit my thesis for second consideration.

Actually I'm glad that you weren't testing - it would have been too much
like trolling.
 
S

S.Tobias

int i;
n = is_moon_waxing ? NULL : &i;
Perhaps if you read the defect reports that I referenced you would. Those
reports are #012, #076 and #106, the one dealing specifically with this
case being #076.
[snip]

It just means that the Standard had its problems, that's why they
keep correcting it. It still has...

I only have access to n869 and the C89 draft. n869 has no 6.2.2.1 and
the C89 draft has different numbering, so I don't know where to find that
paragraph. Quote it if you can.

In the C89 draft that would be 3.2.2.1 par 2. It's basically
the same as C99 6.3.2.1#2.
Given the defect reports though, the behaviour required under C90 is clear.
I don't see any cv in C90 except that `*n' is
not strictly an lvalue, which cannot (in general) be established at
compile time (due to poor definition of an lvalue).

That's significant enough to prevent the program from being conforming, so
why downplay it?

Defect report #076:
"Subclause 6.3.3.2 requires the operand of &to be an lvalue; NULL is
not an lvalue. [...] [Therefore t]he use of [the construct *n where n ==
NULL] prevents a program from being strictly conforming..."

Yes, I can only agree with it. And all compilers should issue
whatever diagnostic each time, just in case, or else they'll risk
violating the Standard.
How could evaluating "*" - in the case where "&*" is not a no-op -
not result in taking a value?

Evaluating "*" and taking a value are two different processes.
1. The result of "*" is an lvalue (or function designator) (6.5.3.2#4).
2. If a (non-array) lvalue is not an operand to "&", "++", "--", "." or
"=", it is "converted to the value stored in the designated object
(and is no longer an lvalue)" (6.3.2.1#2) (that's when the value
is read).
Perhaps you would accept this as a clarification/expansion: at compile
time it is established whether the "*" operator results in an lvalue, but
it cannot always be established that it is legal to evaluate that lvalue.
At runtime evaluating that lvalue may in some cases be illegal.

Also the definition of the indirection operator is lacking in the case of
pointer to incomplete struct.

N869, 6.5.3.2#4:
The unary * operator denotes indirection. If the
operand points to a function, the result is a function
designator; if it points to an object, the result is an
lvalue designating the object. If the operand has type
``pointer to type'', the result has type ``type''. If an
invalid value has been assigned to the pointer, the behavior
of the unary * operator is undefined.74)

In this case, the operand points to neither a function nor an object,
since an incomplete type cannot be an object (as implied by the definition
of lvalue).

Pointers to incomplete types may point to objects. An object is
simply a range of storage (see the definition somewhere in clause 3.).
So all that is specified in this case is the type. This is
where I would have expected to find wording to the effect of "if it points
to an incomplete type, the result is that a syntax error occurs except in
cases where the & operator is immediately applied".

No, the result is just an lvalue of incomplete struct type.
The value is not accessed yet. You have a bad idea of what lvalues
are. They can be incomplete type - see again the definition at the
top of this article.

It's not quite true perhaps; UB is invoked when an lvalue that doesn't
designate an object is evaluated. It seems that by not evaluating "*"
the Standard wants to accomodate invalid pointers (cf. result of "*").
Only in C99 - this is not true in C90 as the defect reports clarify.

Yes, I was writing this only in context of C99.

It's not strange, but without the &* no-op, it's invalid. In 6.3.2.1 as
quoted top of post: "if an lvalue does not designate an object when it is
evaluated, the behavior is undefined." Since *ps represents an incomplete
type, it cannot be an object. Therefore the behaviour is undefined.

Not automatically, only when the lvalue is evaluated.
`*ps' is not an object; `ps' may point to an object, `*ps' is
an lvalue designating that object ("lvalue" == think "pointer").
When operands to "&", lvalues are not evaluated.
So 6.3.2.1 is effectively saying, "an expression with incomplete type is
an lvalue, but cannot be evaluated as one". Which is pretty meaningless
except for the &* case.
(and except when the incomplete lvalue is an array type)

Generally, yes, I think. There doesn't seem much you can do
with incomplete lvalues.

Sorry, I was wrong for C99 here, but it holds for C90, and for other
operators where lvalue is required ("++", "--", "="). See below.
So we could say that *ps is known in advance to be an lvalue but its
evaluation as such is prohibited.

Precisely, that's how I would like to see it.

But note that the C99 Standard handles this in a different manner for
"&" operator (and is slightly inconsistent in words). It says its
operand must be (among others) the result of "*" operator, and "*"
(in "&*") is not evaluated. This way it tries to weasel out of trouble
of special cases (such as in the above DRs). I don't see anything
that is technically wrong with this.

Maybe you have a point above: since "*" is not evaluated, we don't
really know if `*ps' is an lvalue. But OTOH the subexpression `*ps'
is an expression and the question whether it is an lvalue is valid.
Everything indicates that it is.
One more example:
extern struct s s;
struct s *ps = &s;
*ps; //UB
The last line raises UB because lvalue `*ps' is incomplete type
(and cannot be converted to a value; the last sentence of 6.3.2.1).
What I mean to show is that in order to explain why UB arises, first
you have to assert that `*ps' is lvalue (which is actually trivial,
for the result of "*" is always an lvalue, so there's no question
about it here).

All this is very confusing, especially that there's no good
definition of lvalue in the Standard. I get confused too.
You have to have a brain size of a planet to understand C lvalues
- undoubtedly I don't.
 
S

S.Tobias

Tim Rentsch said:
Clearly there are two distinct notions here: a syntactic notion
defining expressions that are (possible) candidates for
modification, and a semantic notion defining expressions that
refer to values but are not themselves values (ie, expressions
that designate objects). The combination of the first notion and
the second notion together with additional constraints such as
non-const-ness, etc, produce what we're used to calling a
<i>modifiable lvalue</i>.

I expect we're stuck with the term "lvalue" for the semantic
notion of designating an object, probably better termed "object
locator" or something similar. It would be an improvement to
give terms and defintions for both notions. What's most
important is that the definition for the object locator property
needs to be redone.

IMHO the semantic notion of lvalue in C is unnecessary and
causes only problems. I think the Std could leave out
the definition of lvalue (it could only describe its
semantic purpose in non-normative text), tag all expressions
as lvalues or values, and keep the words "if an lvalue does not
designate an object when it is evaluated, the behavior is
undefined", and possibly a few other bits. The idea of
an lvalue is used mostly in Constraints.
 

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,780
Messages
2,569,611
Members
45,264
Latest member
FletcherDa

Latest Threads

Top