The object status of function call returns

N

Netocrat

Any comments on the correctness of the statements 1, 2a, 2b, 3 and 4 in
the code below?

If they are correct, then the definition of an object as well as that of
an lvalue is broken in C99 by the following reasoning:

foo() does not return an object, so the return of foo() conceptually is
not stored, yet we are able to obtain a pointer to one of its member
elements, therefore it must be stored, therefore foo() does return an
object, which is a contradiction.

6.5.2.2#5 prevents us from storing and later using foo().ar anyhow, but
C99 seems to be confused about whether conceptually a function returns
something that is stored (Stan Tobias's concept of a temporary variable)
or is just a value.

Both gcc and como online compile this code in strict C99 mode, and both
fault in C90 mode. Como online accepts foo().ar as an isolated statement
but not as an argument to printf.

#include <stdio.h>
struct s {char ar[3];} foo(void)
{
struct s s = {"ab"};
return s;
}

int main (void)
{
/* 1. C90/C99 prohibit this because foo()'s return is not an
* object - 6.8.6.4#3 refers to "the value of the function call"
* rather than "the object returned by the function call". However
* by C99's broken definition it is unintentedly an lvalue */
/* &foo(); */
/* 2a. By C90 foo().ar is not an lvalue so we can't take its
* address.
* 2b. By C99's broken definition it is an lvalue but not an
* object. 6.5.2.3#1 specifically allows foo().ar and
* 6.5.2.3#3 confirms that it is an lvalue */
/* &foo().ar; */
/* 3. By C99, ar decays to char* because of a wording change in
* 6.3.2.1#3 from "lvalue that has type" to "expression that has
* type" so this is valid */
foo().ar;
/* 4. As for (3) */
&foo().ar[0];
/* With gcc in C99 mode this compiles, runs and prints the
* 2 ptrs */
printf("%p\n", (void *)&foo().ar[0]);
printf("%p\n", (void *)foo().ar);

return 0;
}
 
J

Jack Klein

Any comments on the correctness of the statements 1, 2a, 2b, 3 and 4 in
the code below?

If they are correct, then the definition of an object as well as that of
an lvalue is broken in C99 by the following reasoning:

That the definition of lvalue is broken in C99 is an acknowledged
fact. But the proper place to discuss this is really comp.std.c, not
here.
foo() does not return an object, so the return of foo() conceptually is
not stored, yet we are able to obtain a pointer to one of its member
elements, therefore it must be stored, therefore foo() does return an
object, which is a contradiction.

6.5.2.2#5 prevents us from storing and later using foo().ar anyhow, but
C99 seems to be confused about whether conceptually a function returns
something that is stored (Stan Tobias's concept of a temporary variable)
or is just a value.

Both gcc and como online compile this code in strict C99 mode, and both
fault in C90 mode. Como online accepts foo().ar as an isolated statement
but not as an argument to printf.

#include <stdio.h>
struct s {char ar[3];} foo(void)
{
struct s s = {"ab"};
return s;
}

int main (void)
{
/* 1. C90/C99 prohibit this because foo()'s return is not an
* object - 6.8.6.4#3 refers to "the value of the function call"
* rather than "the object returned by the function call". However
* by C99's broken definition it is unintentedly an lvalue */
/* &foo(); */
/* 2a. By C90 foo().ar is not an lvalue so we can't take its
* address.
* 2b. By C99's broken definition it is an lvalue but not an
* object. 6.5.2.3#1 specifically allows foo().ar and
* 6.5.2.3#3 confirms that it is an lvalue */
/* &foo().ar; */
/* 3. By C99, ar decays to char* because of a wording change in
* 6.3.2.1#3 from "lvalue that has type" to "expression that has
* type" so this is valid */
foo().ar;
/* 4. As for (3) */
&foo().ar[0];
/* With gcc in C99 mode this compiles, runs and prints the
* 2 ptrs */
printf("%p\n", (void *)&foo().ar[0]);
printf("%p\n", (void *)foo().ar);

return 0;
}

You are missing the (admittedly non-normative) section on undefined
behavior in Annex J. Specifically:

"— An attempt is made to modify the result of a function call, a
conditional operator, an assignment operator, or a comma operator, or
to access it after the next sequence point (6.5.2.2, 6.5.15, 6.5.16,
6.5.17)."

So your &foo().ar is quite legal, but attempting to use it later by
retaining the pointer produces undefined behavior. Which
implementations are not required to diagnose. In fact, there is
nothing in the standard that requires this to be a valid pointer. It
could be a null pointer, or a random value in a pointer.

There have already been threads on this in comp.std.c, Google the
group.
 
N

Netocrat

Jack said:
[T]he proper place to discuss this is really comp.std.c, not
here.

You are right. My judgement was out and the post was OT.
You are missing the (admittedly non-normative) section on undefined
behavior in Annex J. Specifically:

"- An attempt is made to modify the result of a function call, a
conditional operator, an assignment operator, or a comma operator, or
to access it after the next sequence point (6.5.2.2, 6.5.15, 6.5.16,
6.5.17)."

Actually my reference to "6.5.2.2#5 prevent[ing] us from storing and
later using foo().ar anyhow" was an attempt to include that part of the
standard.
There have already been threads on this in comp.std.c, Google the
group.

I must overcome my tendency to post before searching.
 
L

Lawrence Kirby

Any comments on the correctness of the statements 1, 2a, 2b, 3 and 4 in
the code below?

If they are correct, then the definition of an object as well as that of
an lvalue is broken in C99 by the following reasoning:

foo() does not return an object, so the return of foo() conceptually is
not stored, yet we are able to obtain a pointer to one of its member
elements, therefore it must be stored, therefore foo() does return an
object, which is a contradiction.

There is clearly a temporary object in the execution environment. See
below.
6.5.2.2#5 prevents us from storing and later using foo().ar anyhow, but
C99 seems to be confused about whether conceptually a function returns
something that is stored (Stan Tobias's concept of a temporary variable)
or is just a value.

Both gcc and como online compile this code in strict C99 mode, and both
fault in C90 mode. Como online accepts foo().ar as an isolated statement
but not as an argument to printf.

#include <stdio.h>
struct s {char ar[3];} foo(void)
{
struct s s = {"ab"};
return s;
}

int main (void)
{
/* 1. C90/C99 prohibit this because foo()'s return is not an
* object - 6.8.6.4#3 refers to "the value of the function call"
* rather than "the object returned by the function call". However
* by C99's broken definition it is unintentedly an lvalue */

Where does the standard say that the return value of a function is an
lvalue? The only sensible way to read the definition of lvalue in
6.3.2.1p1 is to say that it specifies the meaning of the term when it is
used in the standard, it is *not* a statement of when an expression is to
be considered an lvalue.
/* &foo(); */
/* 2a. By C90 foo().ar is not an lvalue so we can't take its
* address.
* 2b. By C99's broken definition it is an lvalue but not an
* object.

lvalues exist in the translation environment, objects at in the
execution environment. Since we're tlking about the translation
environment it cannot be an object. However an lvalue in the translation
environment can designate an object in the execution environment.

From above. The standard never talks about the result of an operator being
"an object" even if the result is an lvalue. It is important to
distinguish these 2 concepts. The presence of an lvalue (and the absence
of undefined behaviour) implies the existence of an object at runtime. In
the case of foo().ar[0] the creation of an lvalue is deferred but when it
does come into being it implies the existence of an object at runtime.
6.5.2.3#1 specifically allows foo().ar and
* 6.5.2.3#3 confirms that it is an lvalue */

The . operator doesn't require its left hand operand to be an lvalue. If
it isn't the result isn't either. That is the case here i.e. no lvalue is
involved.
/* &foo().ar; */
/* 3. By C99, ar decays to char* because of a wording change in
* 6.3.2.1#3 from "lvalue that has type" to "expression that has
* type" so this is valid */

& requires its operand to be an lvalue or function designator (or some
other things not relevant here); this is a constraint violation.
foo().ar;
/* 4. As for (3) */
&foo().ar[0];

This is different because the [] operator creates an lvalue, and is also
one of the "other things" specified as the operand of &.
/* With gcc in C99 mode this compiles, runs and prints the
* 2 ptrs */
printf("%p\n", (void *)&foo().ar[0]);
printf("%p\n", (void *)foo().ar);

Those have undefined behaviour in C99 so gcc is at liberty to print the 2
pointers if it wants to.
return 0;
}

Lawrence
 
N

Netocrat

On Tue, 19 Jul 2005 23:25:27 +0000, Netocrat wrote:
#include <stdio.h>
struct s {char ar[3];} foo(void)
{
struct s s = {"ab"};
return s;
}

int main (void)
{
/* 1. C90/C99 prohibit this because foo()'s return is not an
* object - 6.8.6.4#3 refers to "the value of the function
call" * rather than "the object returned by the function
call". However * by C99's broken definition it is unintentedly
an lvalue */

Where does the standard say that the return value of a function is an
lvalue?

The standard does not anywhere explicitly specify that a function is,
or is not, an lvalue.
The only sensible way to read the definition of lvalue in 6.3.2.1p1 is
to say that it specifies the meaning of the term when it is used in the
standard,

That may be sensible, but it leaves the lvalue-ness of some expressions
undefined. I compiled a list of all the cases specifically mentioned
by the standard:

lvalues:
6.5.1#2 an identifier declared as designating an object
6.5.1#4 a string literal
6.5.1#5 a parenthesized expr if the unparenthezised expr is an lvalue
6.5.2.3#3 struct expression members accessed with "." if the struct
expression is an lvalue
6.5.2.3#4 struct expression members accessed with "->"
6.5.2.5#5 a compound literal
6.5.3.2#4 the result of "*" if the operand points to an object
7.5#2 errno

non-lvalues:
6.5.3.2#3 the result of "&*"
6.5.16#3 an assignment expression
Non-normative footnotes: cast, comma and conditional expressions

Amongst others, this list is missing function returns.

So I used the reasoning apparently supported by Pete, Tim Rentsch, and
Michael Mair in the recent thread "gcc: pointer to array": that since
it is italicized, 6.3.2.1#1 contains the sole definition of an lvalue
(I think it's reasonable to add "unless an expression's lvalue-ness is
otherwise specified elsewhere in a normative part of the standard").

Given that the lvalue-ness of function returns and constants is not
elsewhere defined, they match the definition of 6.3.2.1#1 - worse, they
match the definition of "modifiable lvalue".
it is *not* a statement of when an expression is to be considered an
lvalue.

How would you construct such a statement in reference to the standard,
or do you agree that the inability to do so in all cases is one reason
that the C99 lvalue definition is broken?
/* &foo(); */
/* 2a. By C90 foo().ar is not an lvalue so we can't take its
* address.
* 2b. By C99's broken definition it is an lvalue but not an *
object.

lvalues exist in the translation environment, objects at in the
execution environment. Since we're tlking about the translation
environment it cannot be an object. However an lvalue in the translation
environment can designate an object in the execution environment.

From above. The standard never talks about the result of an operator
being "an object" even if the result is an lvalue. It is important to
distinguish these 2 concepts. The presence of an lvalue (and the absence
of undefined behaviour) implies the existence of an object at runtime.
In the case of foo().ar[0] the creation of an lvalue is deferred but
when it does come into being it implies the existence of an object at
runtime.

How then do you respond to my original claim that the wording of
6.8.6.4#3 implies the opposite?

the [] operator creates an lvalue

How would you support that statement using the standard?

Given that you do not accept a function return as an lvalue in C99 and
interpret that an array index always results in an lvalue, I accept the
rest of your reasoning with one query:
/* With gcc in C99 mode this compiles, runs and prints the
* 2 ptrs */
printf("%p\n", (void *)&foo().ar[0]); printf("%p\n", (void
*)foo().ar);

Those have undefined behaviour in C99 so gcc is at liberty to print the
2 pointers if it wants to.

What makes the first printf undefined?

Finally, if you agree with Jack Klein that this is off-topic, please
cross-post and set the follow-up to comp.std.c. It seems borderline
and I am no longer sure either way.
 
N

Netocrat

Netocrat said:
/* With gcc in C99 mode this compiles, runs and prints the
* 2 ptrs */
printf("%p\n", (void *)&foo().ar[0]); printf("%p\n", (void
*)foo().ar);

Those have undefined behaviour in C99 so gcc is at liberty to print the
2 pointers if it wants to.

What makes the first printf undefined?

I'll answer my own question: accessing the result of foo() after the
next sequence point.
 
P

pete

Netocrat said:
I compiled a list of all the cases specifically mentioned
by the standard:

lvalues:
6.5.1#2 an identifier declared as designating an object
6.5.1#4 a string literal
6.5.1#5
a parenthesized expr if the unparenthezised expr is an lvalue
6.5.2.3#3 struct expression members accessed with "." if the struct
expression is an lvalue
6.5.2.3#4 struct expression members accessed with "->"
6.5.2.5#5 a compound literal
6.5.3.2#4 the result of "*" if the operand points to an object

What if the operand is a null pointer or an indeterminate pointer?
 
N

Netocrat

pete said:
What if the operand is a null pointer or an indeterminate pointer?

Then it obviously doesn't point to an object, and 6.5.3.2#4 says
nothing about the lvalue-ness of the result. By your reasoning as I
understand it, in this case "the" definition of 6.3.2.1#1 must be
considered, by which we still classify the result as an lvalue.

By both 6.5.3.2#4 and 6.3.2.1#1 the behavior is undefined.
 
T

Tim Rentsch

Netocrat said:
[snip]

So I used the reasoning apparently supported by Pete, Tim Rentsch, and
Michael Mair in the recent thread "gcc: pointer to array": that since
it is italicized, 6.3.2.1#1 contains the sole definition of an lvalue
(I think it's reasonable to add "unless an expression's lvalue-ness is
otherwise specified elsewhere in a normative part of the standard").

I feel obliged to jump in and say something here, since apparently I'm
at least partly responsible for the confusion.

What the standard *means* in 6.3.2.1 is something like "an lvalue is
something that has the property that ..., and whether or not something
is an lvalue is defined in other places in the standard." That isn't
what it *says*, but that's what it means.

I'm one of the people who believes that the C standard document should
say what it means and mean what it says, and when it doesn't then that
should be pointed out. Other people have different ideas, such as "it
should have only one sensible interpretation", which I don't really
disagree with, except that saying what it means and meaning what it
says should be a pre-requisite, since otherwise whether there is only
one sensible interpretation (not to mention what the interpretation
is) seems too dependent on who is doing the interpreting.

For these reasons I responded to the discussion about the definition
of lvalue. Probably I should have posted that to comp.std.c; but,
I'm usually reluctant to change newsgroups mid-thread since I'm never
sure who read which newsgroup.

Meanwhile, back in *this* newsgroup, an lvalue should be understood to
mean only those expressional forms that are identified as lvalues in
sections 6.5.x (and probably 6.7.something, but I'm not going to look
up the reference). The definition in 6.3.2.1 should be read not as a
definition but merely as a remark about what sorts of things are in
the realm of the term "lvalue".

And, in case any committee members are listening, please clean up the
language in 6.3.2.1#1 to make it clear that the normative text that
defines whether or not some particular thing is an lvalue is given in
the various sections relating to expressions, etc. Similar deferrals
of defining properties are done in other places in the standard (eg
6.2.7#1 for compatible type); the definition of lvalue deserves no
less.
 
P

pete

Netocrat said:
Then it obviously doesn't point to an object, and 6.5.3.2#4 says
nothing about the lvalue-ness of the result. By your reasoning as I
understand it, in this case "the" definition of 6.3.2.1#1 must be
considered, by which we still classify the result as an lvalue.

That makes it easier for your compiler to tell you
if you don't have an lvalue where you need one.
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top