473,396 Members | 1,714 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,396 software developers and data experts.

gcc: pointer to array

Hi all,

I have a question regarding the gcc behavior (gcc version 3.3.4).

On the following test program it emits a warning:
#include <stdio.h>

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

void print1 (const int* p, size_t cnt)
{
while (cnt--)
printf ("%d\n", *p++);
}

void print2 (const int (*p)[5])
{
size_t cnt;
#if 0
// prohibited:
(*p)[0]=0;
#endif
for (cnt=0; cnt<5; cnt++)
printf ("%d\n", (*p)[cnt]);
}

int main()
{
printf ("test begin\n");

print1 (aInt2, sizeof(aInt2)/sizeof(aInt2[0]));
print2 (&aInt3); // <-- warns here

printf ("test end\n");
return 0;
}

The warning is:
MOD.c: In function `main':
MOD.c:: warning: passing arg 1 of `print2' from incompatible pointer type

Why is that? How is this different from using print1 (aInt2, ...)? All I
want to do is to explicitly show that the pointer is to an array of 5
entries and ensure that it's not modified in anyway inside print2(). However
the #if 0'd assignment operator inside print2() is correctly handled by
gcc -- an error is generated:
MOD.c: In function `print2':
MOD.c:: error: assignment of read-only location
What's wrong with gcc?

There's another thing about the pointer to the array is... The compiler
doesn't generate a warning if I change in print2()
for (cnt=0; cnt<5; cnt++)
to
for (cnt=0; cnt<6; cnt++)
and lets me access (*p)[5], though I think at least a warning should be
generated here.
It doesn't warn me if I put
aInt3[6] = 0;
into main(). But in both cases the compiler "knows" the real size of the
array, still no warning...
Is this OK???

Thanks,
Alex
P.S. I compile it as gcc MOD.c -Wall -oMOD.exe
Nov 15 '05
204 12853
Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 13:39:16 +0000, S.Tobias wrote:
In comp.lang.c Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 02:47:13 -0700, Andrey Tarasevich wrote:
Netocrat wrote: ...
> Neither of these examples removes a const attribute from an lvalue, which
> is impossible by definition. An lvalue can be assigned to.

Huh? No. By definition, lvalue is something that has address in storage. In
general case lvalue cannot be assigned to. Only _modifyable_ lvalue can be
assigned to, but there are also non-modifyable lvalues.

Right, my concept of lvalue was slightly out. Non-modifiable being for
example arrays and structs.
I'm not sure it's that simple with storage for lvalues. Take `register'
variables for an example, or pointers to objects whose lifetime has
finished (dereferencing such a pointer is an lvalue, whether the object
exists or not).


The C89 draft says:

"The unary * operator denotes indirection. If the operand points to ... an
object, the result is an lvalue designating the object... If an invalid
value has been assigned to the pointer, the behavior of the unary *
operator is undefined."

Since in this case the pointer has an invalid value it would be illegal to
dereference it; the question of whether or not the result would be an
lvalue is moot.


It is not moot. Being an [rl]value is a compile-time property, and
must be known before the program is run. A compiler must accept
this:
int *p = 0;
/*...*/
*p = 7;
(unless it can prove `p' will always be null ptr). Whatever value
`p' has, `*p' is always an lvalue. If we couldn't make this
statement then the *constraint* that the left operand of "="
be a (modifiable) lvalue would be undiagnosable (IOW every
implementation would be non-conforming... well... unless it
issued any diagnostic each time it were run).

The quote that you have brought in, simply defines the result of
indirection operator; it "explicitly" does not define it when ptr does
not point to an object, in which case it doesn't say if the expression
is an lvalue or not. I think that either mentioning it's an lvalue
in the first case is redundant, or not doing so in the second case
is a flaw. It is difficult to say, because the lvalue definition
in the Standard is seriously flawed anyway.

My "working" definition is: lvalue is an expression that potentially
locates an object. (It might be not strictly correct in a few aspects.)
There're a few useful remarks on lvalues in the Rationale.
Consider also this example:

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
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.
But does it take any storage? I think C++ is more verbose about
temporaries; they can be optimized out, which is unspecified.
I don't think C even has an idea of a temporary.


It doesn't occur in the context of variable storage in either draft.
OTOH, f().a decays into a pointer to its first element, therefore
the array the pointer points to must be an object, therefore it
must (temporarily) take some storage. Is that a right conclusion?


If f() were a modifiable lvalue which in this context it does not appear
to be, then I would see nothing wrong with your reasoning.


It doesn't have to be.
int a[1];
a[0];
`a' is not a modifiable lvalue, `a[0]' is (cf. Rationale).
However given
that there's no means of later reading back the assignment, it's really a
no-op and again the question is moot: nothing changes whether the
assignment occurs or not. Perhaps that's why C99 allows it.


I don't quite understand what you mean here. I have written the assignment
to illustrate that `f().a[0]' is an lvalue (a compiler must accept it).
The UB is invoked neither by this expression, nor the assignment itself;
what invokes it is an attempt to modify the return of a function,
cf. 6.5.2.2#5 (hence by the assignment too, but rather indirectly).

Anyway, as to the storage of temporaries in C, I'm going to start
a separate thread on the issue later today.

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #101
[F'ups to clc]

In comp.lang.c Peter Nilsson <ai***@acay.com.au> wrote:
S.Tobias wrote:
In comp.lang.c Peter Nilsson <ai***@acay.com.au> 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. [...]
> ...
> }
>
> char *foo(char *x)
> {
> return x;
> }


--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #102
[F'ups to clc]

In comp.lang.c Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 15:03:21 +0000, Richard Bos wrote:


[snip]
Other non-modifiable lvalues are incomplete types (unfinished
variable-length arrays, declared but not yet defined structs, etc.),


I'm with you on all of those bar the last. Surely by the time you attempt
to access a struct variable as an lvalue, the struct must have been
defined?


I don't know if you're aware of this, but an expression with
an incomplete structure type can also be an lvalue. (There're might
be some problems with obtaining such expressions though, which
makes it a bit of an academic issue.)
struct s;
struct s *ps;
& * ps;
The sub-expression `*ps' has an incomplete struct type, and is an lvalue.

Other example of incomplete-type lvalue is an array:
int a[7];
int (*pa)[] = &a;
*pa;
`*pa' is an lvalue with the incomplete type `int[]'.

The idea of lvalue has little to do with accessing an object it
designates; size is not included in the notion. An lvalue is
an object locator. Read C99 Rationale for more information.

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #103
On Fri, 15 Jul 2005 14:32:42 +0000, S.Tobias wrote:
[F'ups to clc]

In comp.lang.c Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 15:03:21 +0000, Richard Bos wrote:
[snip]
Other non-modifiable lvalues are incomplete types (unfinished
variable-length arrays, declared but not yet defined structs, etc.),


I'm with you on all of those bar the last. Surely by the time you attempt
to access a struct variable as an lvalue, the struct must have been
defined?


I don't know if you're aware of this, but an expression with
an incomplete structure type can also be an lvalue.


I wasn't specifically aware, but neither was I was disagreeing with
that assertion (admittedly my wording is unclear in that regard), my
point was actually that I couldn't see a way in which such an lvalue
could occur. Checking the standard, you're right that it is an acceptable
lvalue:

N869, 6.3.2.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."
(There're might
be some problems with obtaining such expressions though, which
makes it a bit of an academic issue.)
Which was exactly the point I was making - in fact I can't imagine any way
to validly access an incomplete structure as an lvalue. So it would seem
to be an entirely academic issue.
struct s;
struct s *ps;
& * ps;
The sub-expression `*ps' has an incomplete struct type, and is an lvalue.
Here you dereference a pointer to an incomplete struct which I can't see
ever having legal meaning (I can't find anything in the standard that
strictly forbids it, but intuitively it's wrong to me). Gcc agrees with
me in both C90 and C99 modes:

error: dereferencing pointer to incomplete type

Not that you seem to have much faith in gcc's diagnostics.
Other example of incomplete-type lvalue is an array:
int a[7];
int (*pa)[] = &a;
*pa;
`*pa' is an lvalue with the incomplete type `int[]'.
This seems fine because int[] when accessed as an lvalue decays to int*,
which is a complete type. Whereas in the case of *ps from the previous
example, there's no sense in which "struct s" can be considered to be
complete or to decay into a complete type.
The idea of lvalue has little to do with accessing an object it
designates; size is not included in the notion. An lvalue is an object
locator. Read C99 Rationale for more information.


I'm glad you directed me there because I found this quote:

"A difference of opinion within the C community centered around the
meaning of lvalue, one group considering an lvalue to be any kind of
object locator, another group holding that an lvalue is meaningful on the
left side of an assigning operator. The C89 Committee adopted the
definition of lvalue as an object locator. The term modifiable lvalue is
used for the second of the above concepts."

In fact my original understanding of lvalue coincided with the second
definition, which is why I made the assertion that "An lvalue can be
assigned to" that started this sub-thread. I accept that the standard
chose a slightly different definition but I disagree with the decision.
The word "lvalue" implies "left value" implying an expression that is
legal on the left side of an assignment statement implying that it must be
modifiable. So I think a different word should have been chosen - perhaps
"object locator" - and lvalue retained its meaning as assignable object.

Nov 15 '05 #104
Netocrat wrote:
N869, 6.3.2.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."
(There're might
be some problems with obtaining such expressions though, which
makes it a bit of an academic issue.)
Which was exactly the point I was making
- in fact I can't imagine any way
to validly access an incomplete structure as an lvalue.
So it would seem
to be an entirely academic issue.
struct s;
struct s *ps;
& * ps;
The sub-expression `*ps' has an incomplete struct type,
and is an lvalue.


Here you dereference a pointer to an incomplete struct
which I can't see
ever having legal meaning (I can't find anything in the standard that
strictly forbids it, but intuitively it's wrong to me).
Gcc agrees with
me in both C90 and C99 modes:

error: dereferencing pointer to incomplete type
The code he wrote was
&*ps;
The & and the *, cancel each other out.
Not that you seem to have much faith in gcc's diagnostics.


I don't know what code you compiled to achieve that error.

new.c compiles without errors.

/* BEGIN new.c */

#include <stdio.h>

int main(void)
{
struct s;
struct s *ps;
extern struct s structure;
extern int array[];

ps = &structure;
ps = &*ps;
printf( "ps is %p\n", (void *) ps);
printf("&*ps is %p\n", (void *) &*ps);
printf("&structure is %p\n", (void *) &structure);
printf("&array is %p\n", (void *) &array);
return 0;
}

/* END new.c */

--
pete
Nov 15 '05 #105
On Fri, 15 Jul 2005 13:57:21 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 13:39:16 +0000, S.Tobias wrote:
In comp.lang.c Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 02:47:13 -0700, Andrey Tarasevich wrote:
> Netocrat wrote: ...
>> Neither of these examples removes a const attribute from an lvalue,
>> which is impossible by definition. An lvalue can be assigned to.
>
> Huh? No. By definition, lvalue is something that has address in
> storage. In general case lvalue cannot be assigned to. Only
> _modifyable_ lvalue can be assigned to, but there are also
> non-modifyable lvalues.

Right, my concept of lvalue was slightly out. Non-modifiable being
for example arrays and structs.

I'm not sure it's that simple with storage for lvalues. Take
`register' variables for an example, or pointers to objects whose
lifetime has finished (dereferencing such a pointer is an lvalue,
whether the object exists or not).
The C89 draft says:

"The unary * operator denotes indirection. If the operand points to ...
an object, the result is an lvalue designating the object... If an
invalid value has been assigned to the pointer, the behavior of the
unary * operator is undefined."

Since in this case the pointer has an invalid value it would be illegal
to dereference it; the question of whether or not the result would be
an lvalue is moot.


It is not moot.


Perhaps that was an inflammatory thing to say. We both agree that the
dereference is illegal. Let's pretend I hadn't called the question moot
since it is actually the whole point of our discussion.
Being an [rl]value is a compile-time property, and must be known before
the program is run.
Agreed after reading the standard and rationale more closely.

<snip code and explanation>
The quote that you have brought in, simply defines the result of
indirection operator; it "explicitly" does not define it when ptr does
not point to an object, in which case it doesn't say if the expression
is an lvalue or not.
Why back away from your original strong statement that "being an lvalue is
a compile-time property"? From the quote in my other post, the standard
says: "if an lvalue does not designate an object when it is evaluated, the
behavior is undefined." This exactly describes the case we're discussing.
It is both an lvalue and invokes undefined behaviour on being assigned to.
I think that either mentioning it's an lvalue in the first case is
redundant, or not doing so in the second case is a flaw. It is
difficult to say, because the lvalue definition in the Standard is
seriously flawed anyway.

My "working" definition is: lvalue is an expression that potentially
locates an object. (It might be not strictly correct in a few aspects.)
There're a few useful remarks on lvalues in the Rationale.
That seems to be a useful definition.
Consider also this example:

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.


It's perfectly valid as a non-modifiable lvalue; but we both agree that
assigning to it as a modifiable lvalue invokes undefined behaviour.
Perhaps in this case gcc is using the alternate definition of lvalue (that
which I initially asserted of "modifiable lvalue") in which case the
warning is accurate, although why it doesn't also appear in C99 mode I
don't know.
But does it take any storage? I think C++ is more verbose about
temporaries; they can be optimized out, which is unspecified. I don't
think C even has an idea of a temporary.


It doesn't occur in the context of variable storage in either draft.
OTOH, f().a decays into a pointer to its first element, therefore the
array the pointer points to must be an object, therefore it must
(temporarily) take some storage. Is that a right conclusion?


If f() were a modifiable lvalue which in this context it does not
appear to be, then I would see nothing wrong with your reasoning.


It doesn't have to be.
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.
However given
that there's no means of later reading back the assignment, it's really
a no-op and again the question is moot: nothing changes whether the
assignment occurs or not. Perhaps that's why C99 allows it.


I don't quite understand what you mean here.


That's not unreasonable, because I didn't take into account that 6.5.2.2#5
applies in this case and thus I totally ignored your UB comment. I
assumed that this is legal and defined code in C99 on the basis that gcc
didn't produce a diagnostic in C99 mode, which is obviously a less than
reliable basis and wrong in this case.

So I'll reinterpret my statement: assuming that the storage temporarily
exists (which conceptually it does), and pretending that we are not
prohibited from assigning to it by 6.5.2.2#5, the assignment has no effect
anyway (it's a no-op) since we cannot at any later point retrieve the
assigned value (the temporary storage disappears immediately after the
assignment statement).

As for the question being moot, again, pretend that I didn't say that.
I have written the assignment
to illustrate that `f().a[0]' is an lvalue (a compiler must accept it).
No disagreement there.
The UB is invoked neither by this expression, nor the assignment itself;
what invokes it is an attempt to modify the return of a function, cf.
6.5.2.2#5 (hence by the assignment too, but rather indirectly).


Accepted. I didn't spot the problem as I would have immediately in the
simpler case of:

int f(void);
f() = 7; /* UB for the same reason but more obviously so */

<snip>
Nov 15 '05 #106
On Fri, 15 Jul 2005 21:17:49 +0000, pete wrote:
Netocrat wrote:
N869, 6.3.2.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."
> (There're might
> be some problems with obtaining such expressions though, which makes
> it a bit of an academic issue.)


Which was exactly the point I was making - in fact I can't imagine any
way
to validly access an incomplete structure as an lvalue. So it would
seem
to be an entirely academic issue.
> struct s;
> struct s *ps;
> & * ps;
> The sub-expression `*ps' has an incomplete struct type, and is an
> lvalue.


Here you dereference a pointer to an incomplete struct which I can't
see
ever having legal meaning (I can't find anything in the standard that
strictly forbids it, but intuitively it's wrong to me). Gcc agrees with
me in both C90 and C99 modes:

error: dereferencing pointer to incomplete type


The code he wrote was
&*ps;
The & and the *, cancel each other out.


The * is applied before the &. But dereferencing an incomplete structure
is illegal (as I said I can't find where in the standard this is
specified, but I believe it must be as intuitively it makes no sense to me).
Not that you seem to have much faith in gcc's diagnostics.


I don't know what code you compiled to achieve that error.

new.c compiles without errors.

/* BEGIN new.c */

#include <stdio.h>

int main(void)
{
struct s;
struct s *ps;
extern struct s structure;
extern int array[];

ps = &structure;
ps = &*ps;
printf( "ps is %p\n", (void *) ps); printf("&*ps is %p\n", (void
*) &*ps); printf("&structure is %p\n", (void *) &structure);
printf("&array is %p\n", (void *) &array); return 0;
}

/* END new.c */


$ gcc -ansi -pedantic new.c
new.c: In function `main':
new.c:13: error: dereferencing pointer to incomplete type
new.c:15: error: dereferencing pointer to incomplete type
$ gcc -std=c99 -pedantic new.c
new.c: In function `main':
new.c:13: error: dereferencing pointer to incomplete type
new.c:15: error: dereferencing pointer to incomplete type
$ gcc --version
gcc (GCC) 3.3.5-20050130 (Gentoo 3.3.5.20050130-r1, ssp-3.3.5.20050130-1,
pie-8.7.7.1)
<snip copyright>

Nov 15 '05 #107
Netocrat wrote:
The code he wrote was
&*ps;
The & and the *, cancel each other out.


The * is applied before the &.


No.

N869
6.5.3.2 Address and indirection operators

Semantics
[#3] The unary & operator returns the address of its
operand.

If the operand is the result of a
unary * operator, neither that operator nor the & operator
is evaluated and the result is as if both were omitted,
Not that you seem to have much faith in gcc's diagnostics.

After a while here on this newsgroup,
you should get to a point where you're smarter than your compiler
and your compiler is no longer an instructional aid.
I don't know what code you compiled to achieve that error.

new.c compiles without errors.

/* BEGIN new.c */

#include <stdio.h>

int main(void)
{
struct s;
struct s *ps;
extern struct s structure;
extern int array[];

ps = &structure;
ps = &*ps;
printf( "ps is %p\n", (void *) ps);
printf("&*ps is %p\n", (void *) &*ps);
printf("&structure is %p\n", (void *) &structure);
printf("&array is %p\n", (void *) &array); return 0;
}

/* END new.c */


$ gcc -ansi -pedantic new.c


I don't know whether or not
that's how you compile a c file with gcc.

--
pete
Nov 15 '05 #108
pete wrote:

Netocrat wrote:
The code he wrote was
&*ps;
The & and the *, cancel each other out.


The * is applied before the &.


No.

N869


Well, I did some more checking.
They cancel in C99, but I can't find the same words in C89.
/* BEGIN new.c */

#include <stdio.h>

int main(void)
{
struct s;
struct s *ps;
extern struct s structure;
extern int array[];

ps = &structure;
ps = &*ps;
printf( "ps is %p\n", (void *) ps);
printf("&*ps is %p\n", (void *) &*ps);
printf("&structure is %p\n", (void *) &structure);
printf("&array is %p\n", (void *) &array); return 0;
}

/* END new.c */


$ gcc -ansi -pedantic new.c


I don't know whether or not
that's how you compile a c file with gcc.


When I link new.c with other.c, it forms a complete program.

/* BEGIN other.c */

struct s {
int a;
} structure;
int array[1];

/* END other.c */

Are you unable to form a complete program
by compiling new.c and other.c and linking them?

ps is 00413FC8
&*ps is 00413FC8
&structure is 00413FC8
&array is 00413FD8

--
pete
Nov 15 '05 #109
On Fri, 15 Jul 2005 22:16:01 +0000, pete wrote:
Netocrat wrote:
> The code he wrote was
> &*ps;
> The & and the *, cancel each other out.


The * is applied before the &.


No.

N869
6.5.3.2 Address and indirection operators

Semantics
[#3] The unary & operator returns the address of its
operand.

If the operand is the result of a
unary * operator, neither that operator nor the & operator
is evaluated and the result is as if both were omitted,


Under C99 you are correct. gcc (and I) are wrong about the semantics in
that case. But gcc isn't claimed to be 100% C99 compliant anyhow.

Under C90 those semantics aren't mandated and Derek M. Robert's excellent
pdf standards commentary explains on page 1025 that "[t]he responses to DR
#012, DR #076, and DR #106 specified that [code such as int *n = NULL; int
*p = &*n;] were constraint violations", and that in practice some C90
implementations do not optimise &* into a no-op.

In either case it's still true that an incomplete struct type is never
treated as an lvalue, which is my original point. If &* is optimised into
a no-op then *ps never occurs; and if under C90 it is not then *ps is (as
I read things) a constraint violation which gcc correctly faults on.

<snip>
$ gcc -ansi -pedantic new.c


I don't know whether or not
that's how you compile a c file with gcc.


If you want it to do so in ANSI C89-compatible mode and avoid most (all?)
strictly unnecessary diagnostics it is.

Nov 15 '05 #110
On Fri, 15 Jul 2005 22:28:16 +0000, pete wrote:
pete wrote:


<snip comments on C89 addressed in my other post>
> > /* BEGIN new.c */
> >
> > #include <stdio.h>
> >
> > int main(void)
> > {
> > struct s;
> > struct s *ps;
> > extern struct s structure;
> > extern int array[];
> >
> > ps = &structure;
> > ps = &*ps;
> > printf( "ps is %p\n", (void *) ps);
> > printf("&*ps is %p\n", (void *) &*ps);
> > printf("&structure is %p\n", (void *) &structure);
> > printf("&array is %p\n", (void *) &array); return 0;
> > }
> >
> > /* END new.c */
>
> $ gcc -ansi -pedantic new.c


I don't know whether or not
that's how you compile a c file with gcc.


When I link new.c with other.c, it forms a complete program.

/* BEGIN other.c */

struct s {
int a;
} structure;
int array[1];

/* END other.c */

Are you unable to form a complete program
by compiling new.c and other.c and linking them?

ps is 00413FC8
&*ps is 00413FC8
&structure is 00413FC8
&array is 00413FD8


Yes, I am unable. other.c compiles to object.o OK. But gcc won't
compile new.c, regardless of whether I instruct it to:
a) compile it alone
b) compile it simultaneously with other.c
c) compile it and link with other.o.

I get the same 2 diagnostics in both standards modes: "error:
dereferencing pointer to incomplete type".

Replacing the forward declaration of struct s in new.c with the actual
declaration as in other.c works though (as one would expect). It compiles
without warning in both modes and gives results equivalent to yours:

ps is 0xbffff1a4
&*ps is 0xbffff1a4
&structure is 0xbffff1a4
&array is 0xbffff1a0

Nov 15 '05 #111
Netocrat wrote:

On Fri, 15 Jul 2005 22:28:16 +0000, pete wrote:
pete wrote:


<snip comments on C89 addressed in my other post>
> > /* BEGIN new.c */
> >
> > #include <stdio.h>
> >
> > int main(void)
> > {
> > struct s;
> > struct s *ps;
> > extern struct s structure;
> > extern int array[];
> >
> > ps = &structure;
> > ps = &*ps;
> > printf( "ps is %p\n", (void *) ps);
> > printf("&*ps is %p\n", (void *) &*ps);
> > printf("&structure is %p\n", (void *) &structure);
> > printf("&array is %p\n", (void *) &array); return 0;
> > }
> >
> > /* END new.c */
>
> $ gcc -ansi -pedantic new.c

I don't know whether or not
that's how you compile a c file with gcc.


When I link new.c with other.c, it forms a complete program.

/* BEGIN other.c */

struct s {
int a;
} structure;
int array[1];

/* END other.c */

Are you unable to form a complete program
by compiling new.c and other.c and linking them?

ps is 00413FC8
&*ps is 00413FC8
&structure is 00413FC8
&array is 00413FD8


Yes, I am unable. other.c compiles to object.o OK. But gcc won't
compile new.c, regardless of whether I instruct it to:
a) compile it alone
b) compile it simultaneously with other.c
c) compile it and link with other.o.

I get the same 2 diagnostics in both standards modes: "error:
dereferencing pointer to incomplete type".

Replacing the forward declaration of struct s in new.c with the actual
declaration as in other.c works though (as one would expect). It compiles
without warning in both modes and gives results equivalent to yours:

ps is 0xbffff1a4
&*ps is 0xbffff1a4
&structure is 0xbffff1a4
&array is 0xbffff1a0


OK
Thank you.

--
pete
Nov 15 '05 #112
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 13:57:21 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 13:39:16 +0000, S.Tobias wrote: The C89 draft says:

"The unary * operator denotes indirection. If the operand points to ...
an object, the result is an lvalue designating the object... If an
invalid value has been assigned to the pointer, the behavior of the
unary * operator is undefined."

Since in this case the pointer has an invalid value it would be illegal
to dereference it; the question of whether or not the result would be
an lvalue is moot.
It is not moot.


Perhaps that was an inflammatory thing to say.

[snip]

No, it wasn't, really. Indeed lvalue-ness of expressions could depend
on their evaluation (ie. only expressions with valid behaviour are
lvalues). But then being an lvalue would not be diagnosable at compile-
time. Fine. Expressions like `7 = 11' would yield only UB during
run time. Good.

It is not so (fortunately), lvalue property is referred to in Constrains
in many places, therefore it must be known before evaluation of
an expression. The Standard ought to specify it for every expression,
irrespective of its validity.

Dereferencing an object pointer (other than void*) is always an
lvalue (if it isn't then it ought to be). If it appropriate type
(non-const, non-array, etc.), it's a modifiable lvalue. (It is
conceivable to imagine a scenario where it wasn't an lvalue.)
Some expressions are non-lvalues, eg. cast expressions; gcc used
to have an extension where some cast expressions were lvalues.

The expression `*(int*)0' is a modifiable lvalue. It can be diagnosed,
and a compiler could refuse to compile it if it could prove it would
be evaluated. I believe it couldn't reject this code:
if (0) *(int*)0 = 7;

The quote that you have brought in, simply defines the result of
indirection operator; it "explicitly" does not define it when ptr does
not point to an object, in which case it doesn't say if the expression
is an lvalue or not.


Why back away from your original strong statement that "being an lvalue is
a compile-time property"?


I was just acknowledging the fact that the Standard didn't define it.
I think it ought to.
From the quote in my other post, the standard
says: "if an lvalue does not designate an object when it is evaluated, the
behavior is undefined." This exactly describes the case we're discussing.
It is both an lvalue and invokes undefined behaviour on being assigned to.
It's true, but `*(int*)0' should also be an lvalue (apart from invoking UB);
the Standard (probably) does not say so (at least not explicitly).
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.


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*/
}

[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. `a' is not an lvalue, but `a[0]' is.
Similarly, `f().a' is not an lvalue; `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.

So I'll reinterpret my statement: assuming that the storage temporarily
exists (which conceptually it does), and pretending that we are not
prohibited from assigning to it by 6.5.2.2#5, the assignment has no effect
anyway (it's a no-op) since we cannot at any later point retrieve the
assigned value (the temporary storage disappears immediately after the
assignment statement).
Imagine this:

struct s { int a[1]; };

struct s sfunc(void)
{
static const struct s s; //const is optional, but more illustrative here
return s;
}

int main()
{
sfunc().a[0] = 7; //UB
}

The compiler might make an optimization an reuse the storage of `s' for
the return value (ie. not create a temporary storage). I'm sure you
can see what a disaster it would make.

Actually, if "UB" line wasn't an UB, the compiler could not make such
an optimization. I think it's for this reason (so that it can) that
"UB" is UB. It's the same story with the result of "=" operator.
[...] int f(void);
f() = 7; /* UB for the same reason but more obviously so */


Erm... strictly speaking, no, because it won't compile. And if it
would then behaviour of "=" would raise UB, since LHS is not an lvalue.

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #113
pete <pf*****@mindspring.com> wrote:
new.c compiles without errors.
Which compiler/version did you compile it with?
/* BEGIN new.c */

#include <stdio.h>

int main(void)
{
struct s;
struct s *ps;
extern struct s structure;
extern int array[];

ps = &structure;
ps = &*ps;
printf( "ps is %p\n", (void *) ps);
printf("&*ps is %p\n", (void *) &*ps);
printf("&structure is %p\n", (void *) &structure);
printf("&array is %p\n", (void *) &array);
return 0;
}

/* END new.c */


--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #114
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 14:32:42 +0000, S.Tobias wrote: N869, 6.3.2.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."
The `*ps' is not evaluated in this case.

However `ps' is indeterminate, and using it raises UB. It should
be initialized.
struct s;
struct s *ps;
& * ps;
The sub-expression `*ps' has an incomplete struct type, and is an lvalue.


Here you dereference a pointer to an incomplete struct which I can't see
ever having legal meaning (I can't find anything in the standard that
strictly forbids it, but intuitively it's wrong to me).


Well, C is not for mentally healthy people. ;-)
Gcc agrees with
me in both C90 and C99 modes:

error: dereferencing pointer to incomplete type


I think gcc is wrong. On-line como compiles it fine (with
an unrelated warning).
Cut'n'paste version:
int main()
{
struct s *p = 0;
& * p;
}

Other example of incomplete-type lvalue is an array:
int a[7];
int (*pa)[] = &a;
*pa;
`*pa' is an lvalue with the incomplete type `int[]'.


This seems fine because int[] when accessed as an lvalue decays to int*,
which is a complete type.


Yes, however it is an lvalue before this conversion. After it decays
into int*, it's no longer an lvalue.

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #115
S.Tobias wrote:

pete <pf*****@mindspring.com> wrote:
new.c compiles without errors.


Which compiler/version did you compile it with?


MSVC++ 5.0
/* BEGIN new.c */

#include <stdio.h>

int main(void)
{
struct s;
struct s *ps;
extern struct s structure;
extern int array[];

ps = &structure;
ps = &*ps;
printf( "ps is %p\n", (void *) ps);
printf("&*ps is %p\n", (void *) &*ps);
printf("&structure is %p\n", (void *) &structure);
printf("&array is %p\n", (void *) &array);
return 0;
}

/* END new.c */


--
pete
Nov 15 '05 #116
On Sat, 16 Jul 2005 01:05:01 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 14:32:42 +0000, S.Tobias wrote:
N869, 6.3.2.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."


The `*ps' is not evaluated in this case.


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;
However `ps' is indeterminate, and using it raises UB. It should
be initialized.
Agreed.
struct s;
struct s *ps;
& * ps;
The sub-expression `*ps' has an incomplete struct type, and is an lvalue.


Here you dereference a pointer to an incomplete struct which I can't see
ever having legal meaning (I can't find anything in the standard that
strictly forbids it, but intuitively it's wrong to me).


Well, C is not for mentally healthy people. ;-)


So I'm either headed for the asylum or incompetent as a C programmer.
You've given me some quite attractive options. :)

But seriously, clearly *ps on its own is nonsensical; surely you agree
with me on that? 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'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. In
all other cases (C90 implementations that do not perform this no-op),
*ps is meaningless and is properly rejected by the compiler as it is by
gcc.
Gcc agrees with
me in both C90 and C99 modes:

error: dereferencing pointer to incomplete type


I think gcc is wrong. On-line como compiles it fine (with an unrelated
warning).
Cut'n'paste version:
int main()
{
struct s *p = 0;
& * p;
}


Yes perhaps, but on-line como in C90 mode probably treats &* as a
no-op anyhow, without diagnosing the illegality we get when *ps
conceptually occurs (as C90 requires it to). I quote "The New C Standard"
pdf: "...no C90 implementations known to your author diagnosed occurrences
of these constructs [the code I gave above ie p = &*n where n is NULL]".

Of course in C99 mode it is correct.
Other example of incomplete-type lvalue is an array:
int a[7];
int (*pa)[] = &a;
*pa;
`*pa' is an lvalue with the incomplete type `int[]'.


This seems fine because int[] when accessed as an lvalue decays to
int*, which is a complete type.


Yes, however it is an lvalue before this conversion.


Yes, but the fundamental difference between this case and incomplete
struct is that type int[] has intrinsic meaning (an array of unknown
length but known element size and known starting point which can therefore
be indexed). An incomplete struct has no similar meaning.
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".

Nov 15 '05 #117
On Sat, 16 Jul 2005 00:32:38 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 13:57:21 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Thu, 14 Jul 2005 13:39:16 +0000, S.Tobias wrote: The C89 draft says:

"The unary * operator denotes indirection. If the operand points to
... an object, the result is an lvalue designating the object... If
an invalid value has been assigned to the pointer, the behavior of
the unary * operator is undefined."

Since in this case the pointer has an invalid value it would be
illegal to dereference it; the question of whether or not the result
would be an lvalue is moot.

It is not moot.
Perhaps that was an inflammatory thing to say.

[snip]

No, it wasn't, really. Indeed lvalue-ness of expressions could depend
on their evaluation ...


<snip>
It is not so (fortunately), lvalue property is referred to in Constrains
in many places, therefore it must be known before evaluation of an
expression. The Standard ought to specify it for every expression,
irrespective of its validity.

Dereferencing an object pointer (other than void*) is always an lvalue
(if it isn't then it ought to be). If it appropriate type (non-const,
non-array, etc.), it's a modifiable lvalue. (It is conceivable to
imagine a scenario where it wasn't an lvalue.) Some expressions are
non-lvalues, eg. cast expressions; gcc used to have an extension where
some cast expressions were lvalues.
<snip>
The quote that you have brought in, simply defines the result of
indirection operator; it "explicitly" does not define it when ptr does
not point to an object, in which case it doesn't say if the expression
is an lvalue or not.


Why back away from your original strong statement that "being an lvalue
is a compile-time property"?


I was just acknowledging the fact that the Standard didn't define it. I
think it ought to.
From the quote in my other post, the standard
says: "if an lvalue does not designate an object when it is evaluated,
the behavior is undefined." This exactly describes the case we're
discussing. It is both an lvalue and invokes undefined behaviour on
being assigned to.


It's true, but `*(int*)0' should also be an lvalue (apart from invoking
UB); the Standard (probably) does not say so (at least not explicitly).


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." Then the
question of whether dereferencing that address yields a valid object is a
separate issue. This works for the example you gave of a dereferenced
pointer to an expired object. We can take it's address so it is clearly
an lvalue, but dereferencing that address is a separate test which yields
undefined behaviour. It also works for incomplete structs. We can always
take the address of the incomplete struct, so it is clearly an lvalue, but
(I contend that) dereferencing that address is never valid.
> 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.


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*/
^
[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.
Similarly, `f().a' is not an lvalue;
Again, ITYM that it is an lvalue but not a modifiable lvalue.
`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. 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().

<snip>
int f(void);
f() = 7; /* UB for the same reason but more obviously so */


Erm... strictly speaking, no, because it won't compile.


I don't have a complete handle on all the 'wrong behaviour' terminology
and consequences so strictly speaking you may be right. But in the case
of C90 (at least as interpreted by gcc and online como) neither will your
original example using f().a[0] = 7 compile.
And if it
would then behaviour of "=" would raise UB, since LHS is not an lvalue.


Ummm, yes, that was my point.

Nov 15 '05 #118
S.Tobias wrote:
In comp.lang.c Peter Nilsson <ai***@acay.com.au> wrote:
S.Tobias wrote:
In comp.lang.c Peter Nilsson <ai***@acay.com.au> 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.

--
Peter

Nov 15 '05 #119
On Fri, 15 Jul 2005 23:15:05 -0700, Peter Nilsson wrote:
S.Tobias wrote:
In comp.lang.c Peter Nilsson <ai***@acay.com.au> wrote:
> S.Tobias wrote:
>> In comp.lang.c Peter Nilsson <ai***@acay.com.au> 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 mischaracterised your code elsewhere - I see that as advertised it
does remove the const attribute from an lvalue without an explicit cast.

It must compile, but it also raises undefined behaviour. Stan didn't
quote properly to support his valid argument. A little further on...

N869, 6.5.2.2#6
If the function is
defined with a type that does not include a prototype, and
the types of the arguments after promotion are not
compatible with those of the parameters after promotion, the
behavior is undefined, except for [cases that don't apply].

According to compatibility rules, const char * is not compatible with char *:

N869, 6.7.3#9
For two qualified types to be compatible, both shall
have the identically qualified version of a compatible type...

So the behaviour is undefined. The same wording for both paragraphs
occurs in the C89 draft, although it doesn't list the - non-applicable to
this case - exclusions.

In any event, if the original type of the object pointed to were
const-qualified, then modifying that object would be illegal (I know
that's not the point you were making but it's important):

N869, 6.7.3#5
If an attempt is made to modify an object defined with
a const-qualified type through use of an lvalue with non-
const-qualified type, the behavior is undefined.

Nov 15 '05 #120
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.

--
pete
Nov 15 '05 #121
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.

--
pete
Nov 15 '05 #122
pete wrote:

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.
--
pete
Nov 15 '05 #123
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 00:32:38 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 13:57:21 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
> On Thu, 14 Jul 2005 13:39:16 +0000, S.Tobias wrote:
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.
Similarly, `f().a' is not an lvalue;

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.

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #124
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 01:05:01 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 14:32:42 +0000, S.Tobias wrote: N869, 6.3.2.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."
The `*ps' is not evaluated in this case.


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

struct s;
struct s *ps;
& * ps;
The sub-expression `*ps' has an incomplete struct type, and is an lvalue.


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
--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #125
Peter Nilsson <ai***@acay.com.au> wrote:
S.Tobias wrote:
In comp.lang.c Peter Nilsson <ai***@acay.com.au> wrote:
> S.Tobias wrote:
>> In comp.lang.c Peter Nilsson <ai***@acay.com.au> 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.]

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #126
On Sat, 16 Jul 2005 10:45:44 +0000, pete wrote:
pete wrote:

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:
"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.

Nov 15 '05 #127
On Sat, 16 Jul 2005 17:18:54 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 01:05:01 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 14:32:42 +0000, S.Tobias wrote: N869, 6.3.2.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."

The `*ps' is not evaluated in this case.
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.


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.
> struct s;
> struct s *ps;
> & * ps;
> The sub-expression `*ps' has an incomplete struct type, and is an
> lvalue.


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.


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.

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


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.

Nov 15 '05 #128
Netocrat wrote:

On Sat, 16 Jul 2005 10:45:44 +0000, pete wrote:
pete wrote:

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.

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


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

--
pete
Nov 15 '05 #129
On Sun, 17 Jul 2005 00:14:39 +0000, pete wrote:

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

Nov 15 '05 #130
S.Tobias wrote:
Peter Nilsson <ai***@acay.com.au> wrote:
S.Tobias wrote:
In comp.lang.c Peter Nilsson <ai***@acay.com.au> wrote:
> S.Tobias wrote:
>> In comp.lang.c Peter Nilsson <ai***@acay.com.au> 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.

--
Peter

Nov 15 '05 #131
On Sat, 16 Jul 2005 15:58:59 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 00:32:38 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Fri, 15 Jul 2005 13:57:21 +0000, S.Tobias wrote:
> Netocrat <ne******@dodo.com.au> wrote:
>> On Thu, 14 Jul 2005 13:39:16 +0000, S.Tobias wrote:
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?


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

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

<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.
<snip my false reasoning>
`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.
Similarly, `f().a' is not an lvalue;

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>

Nov 15 '05 #132
Netocrat wrote:
How about this for an alternate
definition of an lvalue:


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

http://groups-beta.google.com/group/...f56f520?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?

--
pete
Nov 15 '05 #133
pete wrote:
Netocrat wrote:

How about this for an alternate
definition of an lvalue:

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

http://groups-beta.google.com/group/...f56f520?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. :-)

--
Joe Wright
"Everything should be made as simple as possible, but not simpler."
--- Albert Einstein ---
Nov 15 '05 #134
On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:
Netocrat wrote:
>> How about this for an alternate
>> definition of an lvalue:

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

http://groups-beta.google.com/group/...f56f520?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.

Nov 15 '05 #135
Netocrat wrote:

On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:
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.


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

--
pete
Nov 15 '05 #136
Netocrat <ne******@dodo.com.au> writes:
On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:
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:


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


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.
Nov 15 '05 #137
On Sun, 17 Jul 2005 15:16:24 +0000, pete wrote:
Netocrat wrote:

On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:
> 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.


No.


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.

<snip>
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.
Did I pass?


No.


I resubmit my thesis for second consideration.

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

Nov 15 '05 #138
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 17:18:54 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 01:05:01 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
> On Fri, 15 Jul 2005 14:32:42 +0000, S.Tobias wrote: N869, 6.3.2.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."

The `*ps' is not evaluated in this case.

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; int i;
n = is_moon_waxing ? NULL : &i; p = &*n; CV in C90? It is undiagnosable here.

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.

[snip]

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

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.


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


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 is UB
if an lvalue doesn't designate an object, and it would be UB if "*" were
evaluated in this case;
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 "*").
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.


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

struct s;
>> struct s *ps;
>> & * ps;
>> The sub-expression `*ps' has an incomplete struct type, and is an
>> lvalue.
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.

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


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.

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #139
Tim Rentsch <tx*@alumnus.caltech.edu> wrote:
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.

--
Stan Tobias
mailx `echo si***@FamOuS.BedBuG.pAlS.INVALID | sed s/[[:upper:]]//g`
Nov 15 '05 #140
Netocrat wrote:

On Sun, 17 Jul 2005 15:16:24 +0000, pete wrote:
Netocrat wrote:

On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:

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


No.


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


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

--
pete
Nov 15 '05 #141
On Sun, 17 Jul 2005 12:09:39 -0700, Tim Rentsch wrote:
Netocrat <ne******@dodo.com.au> writes:
On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:
> 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:


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.
'(3+4)',
As I argued above and in my previous reply to Pete
(<pa****************************@dodo.com.au>) 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.
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.


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.

Nov 15 '05 #142
On Mon, 11 Jul 2005 09:22:08 +1000, Netocrat <ne******@dodo.com.au>
wrote:
On Mon, 11 Jul 2005 00:53:27 +0400, Alexei A. Frounze wrote:

<snip>
Anyway, can you suggest a declaration of the function's argument that
would simultaneously satisfy:
- being a pointer to an array of a fixed number of elements, say, 5
integers - this array is to be constant, in other words, its elements
can't be altered by using the pointer


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[i] 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)[i] 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
Nov 15 '05 #143
On Mon, 18 Jul 2005 02:21:44 +0000, Dave Thompson wrote:
On Mon, 11 Jul 2005 09:22:08 +1000, Netocrat <ne******@dodo.com.au>
wrote:
On Mon, 11 Jul 2005 00:53:27 +0400, Alexei A. Frounze wrote:

<snip>
> Anyway, can you suggest a declaration of the function's argument that
> would simultaneously satisfy:
> - being a pointer to an array of a fixed number of elements, say, 5
> integers - this array is to be constant, in other words, its elements
> can't be altered by using the pointer


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[i] 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)[i] 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.

Nov 15 '05 #144
On Mon, 18 Jul 2005 00:53:31 +0000, pete wrote:
Netocrat wrote:

On Sun, 17 Jul 2005 15:16:24 +0000, pete wrote:
> Netocrat wrote:
>>
>> On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:
>
>> > 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.
>
> No.


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


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.

Nov 15 '05 #145
On Sun, 17 Jul 2005 20:53:54 +0000, S.Tobias wrote:
Tim Rentsch <tx*@alumnus.caltech.edu> wrote:
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.


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.

Nov 15 '05 #146
Netocrat wrote:
On Mon, 18 Jul 2005 00:53:31 +0000, pete wrote:

Netocrat wrote:
On Sun, 17 Jul 2005 15:16:24 +0000, pete wrote:
Netocrat wrote:

>On Sun, 17 Jul 2005 09:57:38 +0000, pete wrote:

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

No.

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


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.


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
--
E-Mail: Mine is an /at/ gmx /dot/ de address.
Nov 15 '05 #147
Netocrat <ne******@dodo.com.au> writes:
On Sun, 17 Jul 2005 12:09:39 -0700, Tim Rentsch wrote:
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.

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.


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.
Nov 15 '05 #148
On Sun, 17 Jul 2005 20:25:35 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 17:18:54 +0000, S.Tobias wrote:
Netocrat <ne******@dodo.com.au> wrote:
On Sat, 16 Jul 2005 01:05:01 +0000, S.Tobias wrote:
> Netocrat <ne******@dodo.com.au> wrote:
>> On Fri, 15 Jul 2005 14:32:42 +0000, S.Tobias wrote: N869, 6.3.2.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."
>
> The `*ps' is not evaluated in this case.

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; int i;
n = is_moon_waxing ? NULL : &i; p = &*n; 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.
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.

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


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.

<snip>
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?


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?

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


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


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 is UB
if an lvalue doesn't designate an object, and it would be UB if "*"
were evaluated in this case;
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.
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.


Yes, I was writing this only in context of C99.
>>> struct s;
>>> struct s *ps;
>>> & * ps;
>>> The sub-expression `*ps' has an incomplete struct type, and is an
>>> lvalue.
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.

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.


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


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

<snip>
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?

<snip example>
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.

Nov 15 '05 #149
On Mon, 18 Jul 2005 00:10:22 -0700, Tim Rentsch wrote:
Netocrat <ne******@dodo.com.au> writes:
On Sun, 17 Jul 2005 12:09:39 -0700, Tim Rentsch wrote:
<snip> 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?

Nov 15 '05 #150

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

8
by: pemo | last post by:
I've just been trying out the Watcom compiler from http://www.openwatcom.org, and almost immediately compiled some working source that errored. The code is char buffer; ...
34
by: thibault.langlois | last post by:
Hello, I get a warning when I compile: #include <string.h> int main (int argc, char ** argv) { char * s; s = strdup("a string"); return 0; }
7
by: vippstar | last post by:
Today I got a confusing message from gcc (I'm aware, those don't break conformance ;-) In function 'main': 7: warning: format '%d' expects type 'int', but argument 2 has type 'char (*)' The...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.