gcc: pointer to array

S

S.Tobias

Netocrat said:
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.
It doesn't occur in the context of variable storage in either draft.


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

S.Tobias

[F'ups to clc]

In comp.lang.c Peter Nilsson said:
S.Tobias said:
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. [...]
 
S

S.Tobias

[F'ups to clc]

In comp.lang.c Netocrat said:
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.
 
N

Netocrat

[F'ups to clc]

In comp.lang.c Netocrat said:
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.
 
P

pete

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


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.


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

Netocrat

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.

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.
It doesn't occur in the context of variable storage in either draft.


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

Netocrat

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

pete

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

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

pete

pete said:

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
 
N

Netocrat

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

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

Netocrat

pete wrote:
/* 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
 
P

pete

Netocrat said:
pete wrote:
/* 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.
 
S

S.Tobias

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

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

S.Tobias

pete said:
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 */
 
S

S.Tobias

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

pete

S.Tobias said:
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 */
 
N

Netocrat

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

Netocrat

Netocrat said:
Perhaps that was an inflammatory thing to say.
[snip]

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

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.


I was just acknowledging the fact that the Standard didn't define it. I
think it ought 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().

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

Peter Nilsson

S.Tobias said:
In comp.lang.c Peter Nilsson said:
S.Tobias said:
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.
 
N

Netocrat

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

char *foo();

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

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

C99 has different wording from N869.

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

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

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

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

Ask a Question

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

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

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,181
Messages
2,570,970
Members
47,536
Latest member
VeldaYoung

Latest Threads

Top