Returning a struct from a function - strange behavior

K

Keith Thompson

CBFalconer said:
But what if you have a function f that takes two (struct s)s as
parameters. You want to write:

f(g(1).a, g(2).b)

(remember that the limitation is against using sub-portions of
those returned structures). I maintain you should write:

gv1 = g(1); gv2 = g(2);
f(gv1.a, gv2.b);

and nobody is confused, including the compiler.

You're being a bit unclear on what the types of gv1, gv2, f(), and g()
are. Given the code you presented, I think that there have to be two
struct types. If f() takes two arguments of type struct s, the g must
return a result of type, let's say, struct t, which has two members of
type struct s, called a and b.

Adding these declarations, here's a complete program that's consistent
with your code snippet. It's a bit more vertically compressed than my
usual style. In practice, both structs could have more members.

#include <stdio.h>

struct s { int x; };

struct t { struct s a; struct s b; };

void f(struct s arg1, struct s arg2) {
printf("%d %d\n", arg1.x, arg2.x);
}

struct t g(int n) {
struct t result;
result.a.x = n;
result.b.x = n + 1;
return result;
}

int main(void) {
f(g(1).a, g(2).b);
return 0;
}

Are you saying that, *as a matter of programming style*, it would be
better to assign the values returned by g() to temporaries before
passing their members to f()? If so, I'm not necessarily going to
disagree. But it really has nothing to do with what's being
discussed. This code is perfectly valid and unambiguous, and has been
at least since C89. I'd be surprised if any compiler were "confused"
by it. There are no arrays (ignoring the format string), and no
function results or parts thereof used as lvalues.

Using a member of a struct value returned by a function is already
legal, and is not the problem we're discussing.

If you're suggesting that an implementation should be allowed to
reject or mis-handle this code, then you're suggesting a change that
would break existing C90 and C99 code. (I don't believe you really
mean that.)

This whole discussion isn't about programming style, it's about what
the language requires compilers to implement. And it's about a very
specific case, a function returning a struct or union that has an
array member, and a caller that refers to that array member within the
expression containing the call. If you want to make a relevant point,
you're going to have to use a relevant example -- and I'd appreciate
it if you'd provide any necessary declarations so we don't have to
reconstruct them.
 
R

Richard Bos

Anand Hariharan said:
My English is very likely not as good as yours, but ITYM "... that
/loses/ a lot ...".

Skitt's Law: any usenet post correcting a spelling error shall itself
contain a spelling error. (And the corrollary: If no error occurs
inadvertently, it is incumbent on the corrector to provide it manually.)

Richard
 
W

Willem

Richard Bos wrote:
)
)> On Wed, 08 Oct 2008 13:17:00 -0700, (e-mail address removed) wrote:
)>
)> > jacob navia wrote:
)> >>
)> >> You do not give a dam about my work if it is not to denigrate it,
)> >
)> > Note: that's 'damn', not 'dam'. That is NOT a criticism, my French is
)> > far worse than your English; I just thought you should know. Swearing
)> > like that looses a lot of it's intended impact when you misspell the
)> > words.
)>
)> My English is very likely not as good as yours, but ITYM "... that
)> /loses/ a lot ...".
)
) Skitt's Law: any usenet post correcting a spelling error shall itself
) contain a spelling error. (And the corrollary: If no error occurs
) inadvertently, it is incumbent on the corrector to provide it manually.)

Apostrophe! Apostrophe!

It's "... a lot of its intended impact ..." (it's is a contraction of it is).

And a case could be made for 'looses' being correct, although it would have
a slightly different meaning.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
N

Nick Keighley

No he hasn't.  What he has done is implement it on one type of CPU
running one OS.  This is much different from being able to do it on
any machine anywhere.  It's still an accomplishment, but it is not
definitive.

Yes but it sounds generally applicable "small" arguments go in
registers "larger" values go in the memory used for arguments and
return values (the stack in other words).

When it comes to implementing compilers Jacob is probably
more knowledgable than you.

I think you need to explain why you consider the proposed
change to C1x to be ineffiecient. Perhaps with some sort
of virtual machine. You need to be more concrete.
 
T

Tim Rentsch

CBFalconer said:
But what if you have a function f that takes two (struct s)s as
parameters. You want to write:



(remember that the limitation is against using sub-portions of
those returned structures). I maintain you should write:

gv1 = g(1); gv2 = g(2);
f(gv1.a, gv2.b);

and nobody is confused, including the compiler.

My point was about the amount of storage required. The
expression

f(g(1).a, g(2).b)

won't take any more storage than

gv1 = g(1); gv2 = g(2);
f(gv1.a, gv2.b);

and if fact there's a good chance that the shorter form will have
a smaller memory footprint than the longer form.

In terms of style - if you find 'f(g(1).a, g(2).b)' confusing
then feel free to use the longer form. I don't find it confusing
and compilers won't either, since the way space is assigned in
the stack frame is just the same as it is non-struct parameters.
 
T

Tim Rentsch

Keith Thompson said:
Tim Rentsch said:
Keith Thompson said:
(e-mail address removed) writes:
The phrase "the array object" implies that there must be an array
object somewhere. In the case we're considering (the return value of
a function that returns a struct with an array member), there is no
array object whose initial element we can point to. Is there other
(normative) wording that clarifies this?

Yes, in N1336. :)

In C90, the section you quoted said that only lvalues with array type
are converted to pointers; C99 relaxed that to allow all array type
expressions to be converted. As you note, that leads to a bit of
cognitive dissonance since an object suddenly appears in the midst of a
value, which doesn't otherwise happen. Nonetheless, you can safely
assume that it's magically created out of the luminiferous aether as
required and mysteriously evaporates again at the next sequence point.
The committee was loathe to say anything more about such objects since
it opens a can of worms (e.g., what storage duration and lifetime they
have), but we bit the bullet for C1X.

[... C90 stuff ...]

I decline to believe that the phrase "the array object" in the C99
standard actually causes such an object to be created (or, more
precisely, imposes a requirement on implementers to arrange for such
an object to be created). In particular, I see no implied guarantee
that "the array object" will continue to exist until the next sequence
point. If the called function returns the value of an object of
struct type, then "the array object" could plausibly refer to the
array member of that object, which could be local to the function and
therefore nonexistent after the function returns.

An object is just a region of memory that can hold values.
The value returned by a function must be held somewhere;
since where ever that is can hold a value, it is an object.
The only question, as you point out, is what its lifetime is.

One interpretation (only slightly perverse) is that in C99
(and probably C90 as well) the lifetime for such an object
must be either static or automatic. (I don't mean to make
the argument, since it's not really a strong argument, but
I think it's worth noting.) The point of the language in
N1336 is not that the result is an object (because it already
must be), but to clarify what the lifetimes of such objects
are.

I disagree.

C, or at least the C abstract machine, distinguishes strongly between
objects and values. An object may hold a value, but a value does not
necessarily require an object to hold it.

A value is the result of evaluating an expresssion. Given, for
example:

int y = 2 * x + 1;

the expression "2 * x" and "2 * x + 1" have values, but those values
don't have objects associated with them. (They might be stored in
memory, but that's an implementation detail.)

And in the following:

int func(void) { return 41; }
...
int x = func() + 1;

there is no object whose value is 41. A function result is just
another expression value. The value specified in a function's return
statement must be communicated somehow to the caller, so that it can
become the result of the function call expression, but again, there's
no implied object. Whether the value happens to be a simple scalar or
some huge structure makes no conceptual difference -- in most cases.

You keep saying there is no object. However, the value must
be stored in the execution environment /somewhere/, and where ever
that storage is fits the definition of object (in 3.14), since it
is storing data, and can represent values.

At some level it doesn't matter (in most cases, as you say) whether
there is an object or not, since either assumption doesn't affect
anything. But I don't see anything wrong or inconsistent with
an interpretation that puts all expression values in objects (of
mostly unspecified lifetimes).

And it wouldn't make any difference in the case under discussion
(accessing an array member of struct value returned from a function)
except for C's (rather odd) rule that an expression of array type is
usually converted to a pointer value. (Note that this isn't a
conversion in the usual sense; an array value consists of the values
of the array elements, and there's no way to get a pointer value from
that other than my compiler magic.)

But since an attempt to refer to an array value implies, because of
The Rule, the existence of an array object, there has to be an object
in this one bizarre case.

I find it simpler to think of all values as residing in objects,
of various lifetimes. Since that interpretation is consistent
with the text of the Standard, it seems more appropriate, at
least to me. The question of whether or not values reside in
objects isn't really an important one, since it's a distinction
without a difference.


<editorial>

It would be nice if the Standard were more consistent in its
usage of the term "object". For example, 6.3.2.1 p 3 uses
the phrase "if the array object has register storage class."
Objects don't have a storage class; variables do.

</editorial>
 
J

James Kuyper

Anand said:
My English is very likely not as good as yours, but ITYM "... that
/loses/ a lot ...".

My spelling has been getting a lot worse lately; I'm getting a bit
worried about that. The spell checker is useless when my error is a
legal word, just not the one I intended.
Someone recently accused me of thinking that I was incapable of making a
mistake. I'd love to be that unaware of my own faults!
 
J

jacob navia

I have written back ends fvor Power PC, for linux (x86) for
a Analog Devices DSP (16 bits), and have done ports to
Sun Sparc. But those versions are for paying customers, not
distributed for free.
Yes but it sounds generally applicable "small" arguments go in
registers "larger" values go in the memory used for arguments and
return values (the stack in other words).

Yes, small structures will be returned in registers
when possible, and registers are getting bigger and
bigger all the time.
 
C

CBFalconer

Keith said:
.... snip ...

Are you saying that, *as a matter of programming style*, it would
be better to assign the values returned by g() to temporaries
before passing their members to f()? If so, I'm not necessarily
going to disagree.

Yes. And I am unaware (but admit it may be so) that present
compilers can handle it. However I consider it unnecessary, and
error prone.
But it really has nothing to do with what's being discussed. This
code is perfectly valid and unambiguous, and has been at least
since C89. I'd be surprised if any compiler were "confused" by it.
There are no arrays (ignoring the format string), and no function
results or parts thereof used as lvalues.

I always consider KISS to be valuable advice.
 
C

CBFalconer

James said:
.... snip ...

Someone recently accused me of thinking that I was incapable of
making a mistake. I'd love to be that unaware of my own faults!

Should we tell your wife about this desire? :)
 
N

Nate Eldredge

Tim Rentsch said:
You keep saying there is no object. However, the value must
be stored in the execution environment /somewhere/, and where ever
that storage is fits the definition of object (in 3.14), since it
is storing data, and can represent values.

I think in this discussion, people are using "object" to mean something
that can be assumed to reside in memory, and whose address can be taken.

For instance, given

int foo(void);
void bar(int *p);

you cannot do

bar(&(foo()));

The value returned from foo() is a value but not an object. If you want
this value to reside in an object you have to explicitly make one.

int x;
x = foo();
bar(&x);

Now of course, an object whose address is never taken need not actually
be stored in memory, or could be optimized out of existence altogether.
But at least conceptually, it resides in memory. If you need it to do
so, the compiler will ensure that it does.
At some level it doesn't matter (in most cases, as you say) whether
there is an object or not, since either assumption doesn't affect
anything. But I don't see anything wrong or inconsistent with
an interpretation that puts all expression values in objects (of
mostly unspecified lifetimes).

There's nothing wrong with it abstractly, but it isn't the way C works.
 
K

Keith Thompson

CBFalconer said:
Yes. And I am unaware (but admit it may be so) that present
compilers can handle it. However I consider it unnecessary, and
error prone.

Ok, as I said, I have no argument with that as a programming style
point -- but it's not what we were talking about. (To be clear, I
wouldn't hesitate to write something like ``func().member'' if it were
convenient, but if you prefer to write ``temp = func(); temp.member'',
that's fine with me. Any concern that a compiler won't handle it
properly is unfounded, but as a style point it's not worth arguing
about.)
I always consider KISS to be valuable advice.

(For those few who might not be familiar with the term, KISS stands
for "Keep It Simple, Stupid".)

KISS is often very valuable advice, but in this case it has led you
completely astray of the point. The *only* thing being discussed in
this thread, going back to the initial post, is accessing an array
member of a struct (or union) returned from a function, and how that
interacts with the usual array-to-pointer conversion. Not only have
you missed that point, you have seemed to be unaware that you've
missed the point.

Do you have anything to say about the actual issue we're discussing?
 
I

Ian Collins

Nate said:
I think in this discussion, people are using "object" to mean something
that can be assumed to reside in memory, and whose address can be taken.

For instance, given

int foo(void);
void bar(int *p);

you cannot do

bar(&(foo()));

The value returned from foo() is a value but not an object. If you want
this value to reside in an object you have to explicitly make one.
It would also be handy to be able to force the compiler to create a
temporary object by taking the address of a return value.
 
C

CBFalconer

Keith said:
.... snip ...


(For those few who might not be familiar with the term, KISS
stands for "Keep It Simple, Stupid".)

KISS is often very valuable advice, but in this case it has led
you completely astray of the point. The *only* thing being
discussed in this thread, going back to the initial post, is
accessing an array member of a struct (or union) returned from a
function, and how that interacts with the usual array-to-pointer
conversion. Not only have you missed that point, you have
seemed to be unaware that you've missed the point.

The thing I am trying to keep simple is the compiler. The more
things it has to take care of, the greater the probability of an
error.
 
K

Keith Thompson

CBFalconer said:
The thing I am trying to keep simple is the compiler. The more
things it has to take care of, the greater the probability of an
error.

Ok. And how exactly do you propose to keep things simple for the
compiler?

Keep in mind that all C compilers already have to handle returning
structs by value, and allowing the caller to access just one member of
the returned value, so there's no opportunity for simplification
there.

Keep in mind also that the case under discussion, dealing with the
implicit array-to-pointer conversion for an array member of a struct
value returned from a function, doesn't seem to be all that difficult
for compilers to handle. And defining its behavior clearly in the
langauge, as n1336, makes things simpler *for the programmer*, by
making one less special case for the programmer to worry about. I
think that making things a little more complicated for compilers and a
little bit simpler for progammers is an excellent tradeoff. Why do
you think it isn't?

Finally, do you understand that the proposed change has *no effect* on
structures that don't contain arrays? Many of your previous comments
seem to have assumed that it does.
 
T

Tim Rentsch

Nate Eldredge said:
I think in this discussion, people are using "object" to mean something
that can be assumed to reside in memory, and whose address can be taken.

The term "object" is defined in the Standard - section 3.14.

For instance, given

int foo(void);
void bar(int *p);

you cannot do

bar(&(foo()));

The value returned from foo() is a value but not an object. If you want
this value to reside in an object you have to explicitly make one.

int x;
x = foo();
bar(&x);

Now of course, an object whose address is never taken need not actually
be stored in memory, or could be optimized out of existence altogether.
But at least conceptually, it resides in memory. If you need it to do
so, the compiler will ensure that it does.

It seems like you're confusing "object" and "lvalue". An lvalue
is a means of referring to an object, but objects can exist
whether or not there are lvalues that refer to them.

There's nothing wrong with it abstractly, but it isn't the way C works.

The point of my comment is that it isn't possible to tell if
that's the way C works. Either interpretation is consistent
with the text of the Standard.
 

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,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top