gcc: pointer to array

P

pete

Netocrat said:
OK, I misinterpreted that bit. You meant (5) rather than the C99
definition of an lvalue.


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).

I know that's the intent of the standard, but
"An lvalue is an expression with an object type"
is what it says.
 
N

Netocrat

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.

At this point in the thread after reading and considering (hopefully most
of the relevant parts of) the standard, I have to disagree. Yes, if
that sentence were the entire definition it would be useless. Yes, I
agree that it should be worded much better. The definition is a little
scattered, nevertheless the concept of lvalue is fully and properly
defined by the standard.

The Rationale explains that the committee decided between giving lvalue
the meaning that "modifiable lvalue" now has, and the definition it
eventually did give lvalue, which it describes as that of an "object
locator". Also note footnote 46 in N869 which says:
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''.
Note the implication that the difference between lvalue and modifiable
lvalue is slight, which is not how you and Pete have been reading the
standard. Your readings accord lvalue status to constants such as 5 and
(3 + 4), which are in no way related to the "original" definition of
lvalue as the left operand of the assignment operator. Rather, the two
terms "locator value" and "object locator" used in footnote 46 and the
rationale respectively both clearly refer to something in storage -
referring to that something as an lvalue implies "finding" where it is
stored.

This "being found in storage" can be equated to taking the address of the
object (with qualifications) which is why I think my alternate definition
of an lvalue as being a valid operand of & (with qualifications) is apt.
I've used this concept to guide my interpretation of the Standard in what
follows.

The rest of footnote 46 says that "[w]hat is sometimes called ``rvalue''
is in this International Standard described as the ``value of an
expression''." I prefer the simplicity of the term rvalue especially in
contrast to lvalue so I'll use it in this post.

Let me summarise my interpretation then:

6.3.2.1#1: Limits lvalue to object type or incomplete type but not void.
The incomplete type is included to allow for things like
incomplete arrays which can still represent "a region of
data storage" (defn of object) even though their size is not
known. I won't comment on incomplete structs because that's
ongoing elsewhere in this thread and I don't want the debate
distract us here.
6.3.2.1#2: Clarifies that the "expression with an object type" must
represent an _actual_ object ("the designated object") for it
to be defined as an lvalue (although 6.3.2.1#1 makes an
exception for indirection of a pointer to an invalid object -
this is clearly an unwanted exception that must be allowed so
that "lvalue-ness" can be determined at compile time).
6.3.2.1#3: Specifies that a decayed array is an rvalue, not an lvalue.
This initially confused me because it could alternately have
been defined that the decayed array still represents the same
object in memory (although given that the original array is a
non-modifiable lvalue, there's no way that treating the decayed
pointer as a non-modifiable lvalue would give it any different
semantics than it now has). It is also consistent with the
other type conversion rule chosen - that the result of a cast
is always an rvalue, not an lvalue.
Footnotes (numbering from N869):
76,83,85) Specify that a cast, a conditional expression and a comma
operator each yield an rvalue, not an lvalue. A cast need not
have been defined this way because it could alternately have
been deemed to represent the same object in storage as that it
was cast from, but just represents it using a different type.
Admittedly that would have introduced a few problems but the
point is that it could have been done. Neither the conditional
expression nor comma case need necessarily have been defined
this way for every case either. Given int x, y, z; the
expression (z?x:y) simplifies to either x or y, both of which
are objects in storage. OTOH it is never possible to interpret
the result of (z?4:5) as an object in storage. So it is
simplest to adopt the conceptual model that these three
operations return rvalues, not lvalues.

Stan Tobias has argued that the Standard should explicitly specify for
each expression type whether it is an lvalue or not. I don't think that
that's a bad idea, but I also think that according to the above, every
expression is already specified. i.e. in the case where an expression
does not certainly represent an object in storage (and is not an
indirection) it is an rvalue, not an lvalue. In any case where there is
potential confusion, the standard clarifies which of rvalue or lvalue
applies.
Almost every expression is
an lvalue, including

I'll take your cases one by one.

As I argued above and in my previous reply to Pete
(<[email protected]>) by my reading the standard
requires that an lvalue designate an object (something in storage), which
this expression does not. Therefore it is an rvalue (this of course
coincides with programmer expectations).
'&i' (after 'int i;'),

Again, &i does not represent a stored object, therefore it is an rvalue
not an lvalue.
and even 'f' (after
'int f(void);'), because a function type is converted to
pointer-to-function, which is an object type.

Still not an lvalue (a function result is not required to be located
somewhere in storage, it is just required that it can be treated as a
value), therefore not an rvalue.
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'

If that were truly the case, the concept of lvalue would indeed be broken.
Considering that the address-off operator is defined in terms of lvalue
then things like &(3 + 4) would be legal, which they clearly should not be.

But I agree that f in sizeof f (as well as sizeof f itself and f in any
other context) is not an lvalue. In particular footnote 74 distinguishes
between a "function designator" and an "lvalue that is a valid operand of
the unary & operator". Given that a function designator is a valid
operand of the unary & operator, this clarifies that a function designator
is not to be considered to be an lvalue.
(but '&f' is still an lvalue).

Again I disagree - it is an rvalue since it does not represent an object
in storage.
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.

This suggests that the standard is not clearly enough worded, although
if you dig deep enough as we have been doing in this thread, you get to
the nuggets.
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 */
Yup.

*s; /* so is this, even when 'struct foo' is an incomplete type */

Yes it is an lvalue but it's not possible to access it as one (which
makes the concept useless): 6.3.2.1#1 says that "if an lvalue does not
designate an object when it is evaluated, the behavior is undefined.".
*v; /* this isn't an lvalue */

True, as explicitly defined. The point of the void type is really to
allow for pointer to void. It has no meaning as an lvalue - _what_ object
in storage is being located? By definition there is no object. And
before you say, "oh but what about &*v?", reread the thread because
we've ascertained that whilst strictly speaking this is a constraint
violation in C90, most implementations will ignore it and do what C99
requires, which is that &* be optimised away and the result no longer be
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>.

That's an excellent description. The second notion defines a plain
lvalue, which accords with what I've described an lvalue to be since an
object in 3.15 is defined as "a region of data storage". You clearly have
the same idea as me of what an lvalue is _intended_ to be, you just
believe that the standard has stuffed up the definition. I hope you'll
accept my reasoning that this isn't the case, although it could be better
worded.
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.

Agreed that object locator is a better term; agreed that the definition is
scattered and not obvious, but as I've tried to show, it isn't broken and
doesn't necessarily have to be redone.
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.

Yes that's true. It doesn't have syntactic relevance but it does have
semantic relevance - i.e. it answers the question "can we consider that
this expression is stored"? OTOH given that my definition relies on the &
operator that question is better answered by referring directly to the &
operator, rather than indirectly through my lvalue definition.
 
D

Dave Thompson

No. To the best of my understanding it is not possible according to the
standard.
Yes. Exactly as the OP did, use a pointer actually to the array as a
whole, not to its elements, which is what you normally use in C and
what (C) programmers normally mean by "pointer to the/an array".
(And use prototype declaration for your function, or as in my brief
examples prototype definition in the same translation unit.)

void func (const int *x)
/* or exactly equivalent void func (const int x[]) */
{ can read but not write x for suitable values of i }
.... int a [5], b[10]; func (a); func (b); /* both OK */ ...

void func (const int (*x)[5])
{ can read but not write (*x) note additional indirection }
.... int a[5], b[10]; func (&a); /* OK */
func (&b); /* incompatible pointer */ ...

c.l.c FAQ 6.13, and the rest of section 6, at the usual places and
http://www.eskimo.com/~scs/C-faq/top.html

- David.Thompson1 at worldnet.att.net
 
N

Netocrat

No. To the best of my understanding it is not possible according to the
standard.
Yes. Exactly as the OP did, use a pointer actually to the array as a
whole, not to its elements, which is what you normally use in C and
what (C) programmers normally mean by "pointer to the/an array".
(And use prototype declaration for your function, or as in my brief
examples prototype definition in the same translation unit.)

void func (const int *x)
/* or exactly equivalent void func (const int x[]) */
{ can read but not write x for suitable values of i }
... int a [5], b[10]; func (a); func (b); /* both OK */ ...

void func (const int (*x)[5])
{ can read but not write (*x) note additional indirection }
... int a[5], b[10]; func (&a); /* OK */
func (&b); /* incompatible pointer */ ...

c.l.c FAQ 6.13, and the rest of section 6, at the usual places and
http://www.eskimo.com/~scs/C-faq/top.html


Yup, that all works fine Dave but what Alexei actually wanted was more
specific and can't be done - at least no one who's responded to this
thread so far has found a way.

He wanted to pass a pointer to an array of a certain number of elements
(i.e. your first prototype is insufficient, but your second is sufficient)
into a function and be guaranteed that the contents of the array could not
be modified within the function. But he also wanted to be able to pass in
parameters of a type that were not const-qualified in any way eg.
int(*)[5], and have them automatically cast to the required type ie const
int (*)[5]. But this seems to be prohibited by C's automatic
const-conversion rules.
 
N

Netocrat

I know that's the intent of the standard, but
"An lvalue is an expression with an object type"
is what it says.

But not _all_ that it says. It has several footnotes, one of which says
that the result of a conditional expression is not an lvalue. By your
reasoning I should ignore that footnote because given int x, y, z, z?x:y
has object type and that's all that matters.

Clearly the footnote should be considered as furthering the very limited
9-word definition you quote, _just_ as the wording of paragraph 2 which
describes "the designated object" furthers this definition by clarifying
that merely having object type is insufficient, the expression must
actually be an object.
 
N

Netocrat

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.

Sure, that's not a bad idea, but I don't think that it's necessary and I
do think that the semantic notion is useful, although not in the broad
way that Pete and Tim have interpreted it as applying to e.g. constants.

I think all would be clear if the first sentence of 6.3.2.1#1 were
changed from:

"An lvalue is an expression with object type or an incomplete type other
than void; if an lvalue does not designate an object when it is evaluated,
the behaviour is undefined"

to:

"An lvalue is an expression that either resolves to an object or to an
incomplete type in storage, or resolves to the indirection of an
expression of type 'pointer to object' or 'pointer to incomplete type'; if
an lvalue does not designate an object when it is evaluated, the behaviour
is undefined"

Note that "object type" has been changed simply to "object". This makes
it explicit that non-objects such as the constant expression (5) and
the return of a function are not defined as lvalues, without having to
read down and find this description tucked away separately in paragraph 2.

The void wording has been removed because a void type properly defined
cannot represent an object. I haven't checked whether it is so defined
elsewhere though so perhaps it is required.

As I interpret it, this is how the standard should be read anyway, but
only after integrating the wording of paragraph 6.3.2.1#2 with paragraph
6.3.2.1#1 and using the footnotes/Rationale to understand that this
integration is proper.
 
M

Michael Mair

Netocrat said:
But not _all_ that it says. It has several footnotes, one of which says
that the result of a conditional expression is not an lvalue. By your
reasoning I should ignore that footnote because given int x, y, z, z?x:y
has object type and that's all that matters.

Clearly the footnote should be considered as furthering the very limited
9-word definition you quote, _just_ as the wording of paragraph 2 which
describes "the designated object" furthers this definition by clarifying
that merely having object type is insufficient, the expression must
actually be an object.

Alas, footnotes in the standard are not normative.

The C99 standard is broken w.r.t. lvalues and if you apply
"very comprehensive" reading to make it not so, you essentially
can make it say anything in other places, by the same methods
of argumentation.
So, one accepts that 5 may be an lvalue and looks to the
modifiable lvalues for something to work with :-/


Cheers
Michael
 
T

Tim Rentsch

Netocrat said:
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.

At this point in the thread after reading and considering (hopefully most
of the relevant parts of) the standard, I have to disagree. [snip]

I think you missed the point of what I was saying. I know that
what was intended is (almost certainly) not what was written.
The problem is that, as written, the standard says almost all
expressions are lvalues. I neglected when giving the original
quote to write:

6.3.2.1

1 An <i>lvalue</i> is an expression with an object type or an
incomplete type other than void; ...

The italics makes this statement the (only) definition, per
section 3, paragraph 1.

Footnotes (numbering from N869):
76,83,85) [cast, conditional expression, comma operators not lvalues]

Thank you for pointing these out (they appear in the standard as
footnotes numbers 85, 92, 94). Even if footnotes were normative
(which they aren't), the presence of these footnotes doesn't
affect the basic point that, per a literal reading of the
standard text, most expressions are lvalues.

Almost every expression is
an lvalue, including

I'll take your cases one by one.
'(3+4)', [snip]
'&i' (after 'int i;'), [snip]
and even 'f' (after
'int f(void);'), because a function type is converted to
pointer-to-function, which is an object type.

The expressions '(3+4)', '&i', and 'f' are lvalues because
they meet the definition given in 6.3.2.1 p1, and no other
writing anywhere in the standard says anything different.

This suggests that the standard is not clearly enough worded, although
if you dig deep enough as we have been doing in this thread, you get to
the nuggets.

The "nuggets", as you call them, are redundant per the definition
and simply further evidence that the writing is broken.

The C standard document is one that should be interpreted
according to a literal reading; anything less means the authors
aren't doing their job. No criticism of committee members or
others closely involved in the process intended - it's extremely
tough to write prose that is exactly literally accurate. But for
a reference/standard document of this kind, that level of
accuracy should always be the goal.


Also, if you wouldn't mind a personal suggestion - your postings
make some good points, but they would be better if you spent a
little longer doing the writing, and made the writing itself a
bit shorter.
 
N

Netocrat

int i;
n = is_moon_waxing ? NULL : &i;
CV in C90? It is undiagnosable here.

OK, so no compiler will be to issue the required diagnostic and
technically a conforming implementation is impossible. That doesn't
prevent it from being a CV according to DR#076.
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...

They may just be problems, but so saying doesn't remove them from the
standard. You still have to consider them as part of C90.
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.

OK, I see it. But C89 draft "3.3.3.2 Address and indirection operators"
says that the operand of unary & must be an object, and the DRs clarify
that that section takes precedence and is the reason that the wording was
changed for N869, so that "6.5.3.2 Address and indirection operators"
specifies that &* is a no-op.

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).> Evaluating "*" and taking a value are two different
processes.

OK, there's a distinction, but the only case I can see it manifest is with
structures as this snippet shows:

int r;
int k[10];
struct {int i; int *j;} s, *sp = &s;
k[3] = 20;
s.j = k;
r = (*sp).j[3];

Considering the final expression in the context of *sp, obviously
6.3.2.1#2 applies and we get back an lvalue. But as you correctly reason,
according to 6.5.3.2#4 it is not immediately converted to the value of
*sp, but rather the "." operator is applied to obtain (*sp).j which is an
lvalue that immediate decays to a non-lvalue pointer and is indexed etc,
yielding a final lvalue which by 6.5.3.2#4 is converted to a value.

So it's true that we do not fully "take the value" of (*sp), but we do
evaluate enough of it to find (*sp).j. At the least this requires
that we determine the address in sp and add the offset of j to it, and
access the contents referenced by j. So... that's quite a bit of "value
taking" ...

An lvalue is an object locator, so to dereference sp into an lvalue you
have to hunt around and find the object that it references, which you
will then do something with. 6.3.2.1#2 describes what that something
might be, and in all cases it requires at least some part of the value
of *sp to be taken.

I'm not sure that there's ever a situation where we "evaluate *" without
"taking its value" in at least some way. Perhaps you can think of one
though. The whole point of expressing an lvalue in code (apart from with
sizeof) is to store into it or read from its storage in some way...
so why _would_ there be a situation where we don't take it's value?

Pointers to incomplete types may point to objects. An object is simply
a range of storage (see the definition somewhere in clause 3.).

OK, I accept that. So 6.5.3.2#4 doesn't always apply, but it may. Instead
we should look to the last sentence of 6.3.2.1#2 which says:

"If the lvalue has an incomplete type and does not have array type, the
behavior is undefined."
No, the result is just an lvalue of incomplete struct type. The value is
not accessed yet.

Well what's the point of using an lvalue in your code if you don't intend
its value to be accessed in any way??? I mean, the concept of an lvalue
is intended to be that of an "object locator". Great, so you've located
an incomplete struct. Now what?
You have a bad idea of what lvalues are. They can be incomplete type -
see again the definition at the top of this article.

I understand that it says that, I just don't understand *why*. I
haven't yet seen a satisfactory example where an lvalue that is of
incomplete struct type actually occurs in any context other than &*ps,
where we've established that it doesn't actually occur since the &* is
optimised away! And if it can occur, then what type of data does it store???
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 "*").

I'm not sure of your point. I see nothing wrong with your original words.
My best interpretation is that you're saying that the &* no-op was added
to prevent conceptual NULL pointer dereferences, which I agree with.
Yes, I was writing this only in context of C99.


Not automatically, only when the lvalue is evaluated.

I assume you're referring again to C89 draft section 3.2.2.1#2:
"Except when it is the operand of .. the unary & operator .. an lvalue ..
is converted to the value stored in the designated object".

So again I'll point out C89 draft 3.3.3.2 which says that the operand of &
must be an object, and which the DRs clarify takes precedence. It doesn't
matter whether or not it is evaluated - it is required to designate an
object.

You argued previously that it is possible that ps points to a struct fully
defined externally. In that case, and that case only, &*ps is not a
constraint violation under C90. So theoretically, this is fully compliant
code:

/** Begin exts1.c **/
struct exts {int i; int j;} es, *esp = &es;
void x(void);
int main(void)
{
x();
}
/** End exts1.c **/

/** Begin exts2.c **/
struct exts *esp;
void x(void)
{
struct exts *stmp;
stmp = &*esp;
}
/** End exts2.c **/

Whereas if we remove the definition of esp from exts1.c, technically this
is undefined behaviour under C90.

In fact gcc gives an error in both cases in both C90 and C99 modes (but
we can assume that it is not properly implementing C99 here).
`*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.

Fair enough. But they still must represent valid objects under C90.
(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.

That's the reason this thread started, so your general agreement is some
sort of closure.

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.

Agreed, but what's the point? An lvalue is meant to be accessed, and
since you agree that an incomplete struct of the form struct *ps can't
be validly accessed as an lvalue, then why specify that it may be one?
Fair enough in the case of C99 there are other cases of incomplete structs
(eg. with a variable array member) which could have validity as an lvalue,
but this isn't the case in C90. So why define it as an lvalue?

All this is very confusing

As is this thread - it's a mess that keeps getting bigger. I'm starting
to repeat myself.
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.

Well I've explained my interpretation in my reply to Tim Rentsch.
 
N

Netocrat

The problem is that, as written, the standard says almost all
expressions are lvalues. I neglected when giving the original
quote to write:

6.3.2.1

1 An <i>lvalue</i> is an expression with an object type or an
incomplete type other than void; ...

The italics makes this statement the (only) definition, per
section 3, paragraph 1.

I can't argue against that. The draft document I use is plain-text
without italics.
The C standard document is one that should be interpreted
according to a literal reading; anything less means the authors
aren't doing their job.

I have accessed a later draft dated May 6 2005 with the same wording.
Can anyone comment on whether any committee members view the definition
as flawed?
 
M

Maxim S. Shatskih

(xpost from alt.os.development)

Thanks! Really a brilliant explanation!

--
Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
(e-mail address removed)
http://www.storagecraft.com

Richard Heathfield said:
[Followups set to comp.lang.c]
The problem is that array itself is a pointer to it's beginning so then

Not quite. The array itself is an array. The /value/ of the array, when that
array's name is used in a value context, is the address of the first
element.
writing &aInt3 would give you pointer to pointer to an array. Solution
is to pass only aInt3 to your function.

The declaration in question was:

int aInt3[5] = {0,1,2,4,9};

When you write &aInt3, you're actually getting the address of the array (the
array is not being used in a value context here); the type is int (*)[5],
i.e. a pointer to an array, not a pointer to a pointer to an array as you
incorrectly claimed. (This is a tricky area - it's intensely logical, but
C's logic doesn't always map perfectly to programmers' first-cut instinct!)

--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
mail: rjh at above domain
 
N

Netocrat

pete said:
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?

I missed your implication that C90's definition is different.

So C90's is too restrictive and C99's too liberal, although the
intended definition is pretty clear.

A useful summary is this Keith Thompson post.

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

In future I'll try to search before replying.
 
S

S.Tobias

Netocrat said:
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).> Evaluating "*" and taking a value are two different
processes.

OK, there's a distinction, but the only case I can see it manifest is with
structures as this snippet shows:

int r;
int k[10];
struct {int i; int *j;} s, *sp = &s;
k[3] = 20;
s.j = k;
r = (*sp).j[3];

Considering the final expression in the context of *sp, obviously
6.3.2.1#2 applies and we get back an lvalue. But as you correctly reason,
according to 6.5.3.2#4 it is not immediately converted to the value of
*sp, but rather the "." operator is applied to obtain (*sp).j which is an
lvalue that immediate decays to a non-lvalue pointer and is indexed etc,
yielding a final lvalue which by 6.5.3.2#4 is converted to a value.
Correct.

So it's true that we do not fully "take the value" of (*sp), but we do
evaluate enough of it to find (*sp).j. At the least this requires
that we determine the address in sp and add the offset of j to it, and
access the contents referenced by j. So... that's quite a bit of "value
taking" ...

Yes. The Standard specifies that that member is read, and only that.
The implementation can read the struct as a whole, or its members
separately if it feels right, provided it doesn't make any visible
difference to the programmer, but this is not specified by the Std
(IOW what is specified is the minimum that must be done to execute
that fragment of a program).
An lvalue is an object locator, so to dereference sp into an lvalue you
have to hunt around and find the object that it references, which you
will then do something with. 6.3.2.1#2 describes what that something
might be, and in all cases it requires at least some part of the value
of *sp to be taken.

I'm not sure that there's ever a situation where we "evaluate *" without
"taking its value" in at least some way. Perhaps you can think of one
though. The whole point of expressing an lvalue in code (apart from with
sizeof) is to store into it or read from its storage in some way...
so why _would_ there be a situation where we don't take it's value?
[snip]
I understand that it says that, I just don't understand *why*. I
haven't yet seen a satisfactory example where an lvalue that is of
incomplete struct type actually occurs in any context other than &*ps,
where we've established that it doesn't actually occur since the &* is
optimised away! And if it can occur, then what type of data does it store???


I see your problem is: why should incomplete type expression to be
an lvalue at all? The answer is: I don't know. The Standard
establishes a language (metalanguage) through which it describes
the C language. If it defines lvalue to be what it defines, you
have to follow it blindly. I myself don't agree with it, I hate it,
I have my own, better opinion on what an lvalue should be.
But in order to interpret correctly other parts of the Standard
you have to accept what it defines.
For example,
extern struct s s;
struct s *sp = &s;
void *vp = &s;
*sp; //UB
*vp; //okay
The first expression yields UB, and the second doesn't. The only reason
for that is that the fist is an lvalue, while the second is not.

The Standard could define C in a different way, specifying things
case by case. Then you would probably write today "why does the Std do
it like that, it's so hard to remember all these cases; there's this
nice idea of lvalue, why not generalize things?". Perhaps someone
tried to generalize, but it wasn't so easy. Perhaps there is
a third way.

And have always in mind that people write standards, and those same
people make errors. It's difficult to discuss things with the lack of
proper definitions. As you know by now, the Std defines as lvalues
expressions that should not be lvalues. IMO that's not the biggest
sin yet. Really bad thing is that the Std contradicts itself, because
some expressions that fulfill the definition of lvalue are explicitly
said not to be lvalues.


I'm not sure of your point. I see nothing wrong with your original words.
My best interpretation is that you're saying that the &* no-op was added
to prevent conceptual NULL pointer dereferences, which I agree with.

Yes, and pointers to (and lvalues designating) one past the last array element
(it's automatically UB if they are operands to "*").


So again I'll point out C89 draft 3.3.3.2 which says that the operand of &
must be an object, and which the DRs clarify takes precedence. It doesn't
matter whether or not it is evaluated - it is required to designate an
object.
Right.

You argued previously that it is possible that ps points to a struct fully
defined externally.
(I needed an incomplete struct pointer that points to a valid object, and
it was impossible to have both complete and incomplete struct type on one line
(well, it is possible, but not as nice).)
In that case, and that case only, &*ps is not a
constraint violation under C90. So theoretically, this is fully compliant
code:

I think so, too.
/** Begin exts1.c **/
struct exts {int i; int j;} es, *esp = &es;
void x(void);
int main(void)
{
x();
}
/** End exts1.c **/

/** Begin exts2.c **/
struct exts *esp; ITYM: extern struct exts *esp;
void x(void)
{
struct exts *stmp;
stmp = &*esp;
}
/** End exts2.c **/

Whereas if we remove the definition of esp from exts1.c, technically this
ITYM remove the initialization of esp to &es
is undefined behaviour under C90.
Yes.

In fact gcc gives an error in both cases in both C90 and C99 modes (but
we can assume that it is not properly implementing C99 here).

como did it fine.

One last remark: UB is not a disaster, it's just lack of a definition.
In this case I think it means C90 was defective, and C99 made a change
for better. I seriously doubt any compiler would produce extra code
in `x()' just waiting when `esp' doesn't point to an object to let
the daemons fly in the face of an unsuspecting programmer.
 
N

Netocrat

On Wed, 20 Jul 2005 00:26:47 +0000, S.Tobias wrote:

I see your problem is: why should incomplete type expression to be an
lvalue at all?

Yes, exactly. There's a need in the case of incomplete array and
partially complete struct expressions, but not but for totally
incomplete struct types.
The answer is: I don't know.

Perhaps comp.std.c would have an answer, but since it's only a minor
redundancy it's so not much of a problem.

For example,
extern struct s s;
struct s *sp = &s;
void *vp = &s;
*sp; //UB
*vp; //okay
The first expression yields UB,

But you seemed to agree (as per the ext1/2.c example below) that this
is only the case if s is not fully defined externally.
and the second doesn't. The only reason for that is that the fist is an
lvalue, while the second is not.

Yes, 6.5.3.2 fails to define the behaviour. vp is a pointer type as
paragraph 2 requires, but no case in paragraph 4 applies, so *vp is
undefined by ommission.

Really bad thing is that the Std contradicts itself, because some
expressions that fulfill the definition of lvalue are explicitly said
not to be lvalues.

Right - the decayed array type and the footnotes on casts,
conditionals, and commas.

(I needed an incomplete struct pointer that points to a valid object,
and it was impossible to have both complete and incomplete struct type
on one line (well, it is possible, but not as nice).)

I think so, too.

ITYM: extern struct exts *esp;

I did, but in this situation extern is redundant, by 6.2.2#5.
ITYM remove the initialization of esp to &es

I didn't, but that's an even better example.
como did it fine.

I can't see a way to compile two separate files on como online so I
can't confirm that. Anyhow it's a difference that can't be detected at
compile time.
 
N

Netocrat

Netocrat said:
I did, but in this situation extern is redundant, by 6.2.2#5.

After reading Me's post in the current thread "extern" I see that
6.9.2#2 applies, and extern is not redundant after all. Excluding it
invokes UB.

<snip rest>
 
S

S.Tobias

Netocrat said:
Yes, 6.5.3.2 fails to define the behaviour. vp is a pointer type as
paragraph 2 requires, but no case in paragraph 4 applies, so *vp is
undefined by ommission.

I think you got it wrong this time. `*sp' is explicitly undefined
by 6.3.2.1#2, because it is an incomplete lvalue, and thus cannot be
converted to a value.

`*vp' is not an lvalue[*], and therefore it doesn't undergo
lvalue-to-value conversion, and it's behaviour is perfectly defined:
it is an expression statement, which is evalueated for its side
effects, and its (nonexistent) value is discarded.

`*vp' is as much valid an expression as a void function call is:
void f(), *pv = &some_object;
f(); //okay
*pv; //okay


[*] Well, actually 6.5.3.2#4 says it is an lvalue, but I think
the Standard is again wrong; I think it deserves a DR, but first
I'll check that this isn't already known.
 
N

Netocrat

I think you got it wrong this time. `*sp' is explicitly undefined
by 6.3.2.1#2, because it is an incomplete lvalue, and thus cannot be
converted to a value.

Yes of course, you are right.
`*vp' is not an lvalue[*], and therefore it doesn't undergo
lvalue-to-value conversion, and it's behaviour is perfectly defined:
it is an expression statement, which is evalueated for its side
effects, and its (nonexistent) value is discarded.

Perhaps you are right. I find the standard a little ambiguous here.
The first two sentences of 6.5.3.2#4 don't apply since by 6.2.5, void
is not an object, it's an incomplete type. That leaves things somewhat
undefined; the third sentence defines the resulting type, but is that
enough definition?

[*] Well, actually 6.5.3.2#4 says it is an lvalue, but I think
the Standard is again wrong; I think it deserves a DR, but first
I'll check that this isn't already known.

6.5.3.2#4 only defines the result as an lvalue "if [the operand] points
to an object". By 6.2.5 void types are never objects.
 
S

S.Tobias

Netocrat said:
`*vp' is not an lvalue[*], and therefore it doesn't undergo
lvalue-to-value conversion, and it's behaviour is perfectly defined:
it is an expression statement, which is evalueated for its side
effects, and its (nonexistent) value is discarded.

Perhaps you are right. I find the standard a little ambiguous here.
The first two sentences of 6.5.3.2#4 don't apply since by 6.2.5, void
is not an object, it's an incomplete type.
[snip]

6.5.3.2#4 only defines the result as an lvalue "if [the operand] points
to an object". By 6.2.5 void types are never objects.

You still confuse lvalues with objects. Lvalue is an expression
(it's those characters - or rather tokens - that you type into your
editor: "*" and "vp" together). Object is an untyped range of bytes
in the run-time, nothing more. Type of an object is the type of
the lvalue that it is accessed with. (There's also an idea of "effective
type", but I don't want to go into details here.)

Lvalue is not an object. Lvalue designates an object (points to an
object, as if with a finger), and it is UB if there's no object.

All of these pointers point to the (same) object:
void *pv = malloc(10); assert(pv);
int *pi = pv;
struct s *ps = pv;
And lvalues designating that object are:
*pv;
*pi; //UB if sizeof(int) > 10
*ps; //UB (because of incomplete type)
 
N

Netocrat

S.Tobias said:
Netocrat said:
`*vp' is not an lvalue[*], and therefore it doesn't undergo
lvalue-to-value conversion, and it's behaviour is perfectly defined:
it is an expression statement, which is evalueated for its side
effects, and its (nonexistent) value is discarded.

Perhaps you are right. I find the standard a little ambiguous here.
The first two sentences of 6.5.3.2#4 don't apply since by 6.2.5, void
is not an object, it's an incomplete type.
[snip]

6.5.3.2#4 only defines the result as an lvalue "if [the operand] points
to an object". By 6.2.5 void types are never objects.

You still confuse lvalues with objects.

Not true. I've been through enough back-and-forth in this thread and
others to at least have _that_ straight.

My argument was actually that since the void type is empty, a void
pointer could be considered as pointing to nothing, no matter what its
value. That is, a void pointer can never actually point to an object.
 
S

S.Tobias

Netocrat said:
Not true.
[snip]

Okay. :-| Then there must be something else wrong with you:
That is, a void pointer can never actually point to an object.

Why do you keep writing things like this? Void pointer *can*
point to *any* object, that's its main purpose. It's that
the object's value cannot be accessed through void type,
the pointer has to be converted to other type (a different
pointer), but the whole expression is still based on the original
void pointer that points to that object, eg:
*(int*)vp;

If a void pointer doesn't point to an object, then where does
it point to?
 

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
474,184
Messages
2,570,973
Members
47,530
Latest member
jameswilliam1

Latest Threads

Top