Index
Home
About
Blog
From: Chris Torek <torek@elf.bsdi.com>
Newsgroups: alt.comp.lang.learn.c-c++,comp.lang.c,comp.lang.c.moderated
Subject: Re: Question re: function evaluated within a 'printf'
Date: 13 Jan 1998 15:27:56 GMT
The original program has a function that returns a "struct" containing
an array, and:
>Richard E. Adams (radams@lvdi.net) wrote:
>> printf("%d\n", concatArr(arr1,arr2).a[1]);
>>[fails to compile, with a mysterious error message]
In article <clcm-19980111-0010@plethora.net> Will Rose <cwr@cts.com> wrote:
>... if the statement is rewritten as the equivalent:
> printf("%d\n", *(concatArr(arr1,arr2).a + 1));
>I get the much clearer warning message:
> -- Pointer arithmetic can only be applied to arrays that are lvalues
>That is, functions don't return lvalues and such a value is needed for
>indexing. How you get around the problem I don't know; probably some
>sort of temporary variable.
This is exactly right.
One of the ways C is peculiar (at least when compared to similar
languages) is in its treatment of pointers and arrays. The comp.lang.c
FAQ has a whole section devoted to pointers vs arrays. In 6.3, it
says pretty much what I used to say a lot on comp.lang.c:
An lvalue of type array-of-T which appears in an
expression decays (with three exceptions) into a
pointer to its first element; the type of the
resultant pointer is pointer-to-T.
Early versions of C did not have "struct"-valued functions. In
this pre-Standard C language, there *were* no "struct"s other than
"lvalues". This corresponded (and still corresponds) fairly well
to a lot of existing computer architectures, where an "lvalue" was
generally something you would find occupying (potentially a lot
of) ordinary memory, while an "rvalue" -- a value stored in an
lvalue -- was something you could deal with in a single machine
register. A "struct" generally contains multiple members holding
lots of separate values. A return value was usually placed in a
machine register, and you could not stick lots of separate values
into one measly register, hence a function could not return a
struct.
So, if we say:
struct X { int a[MAX]; int how_many; } my_object;
we have a structure type containing lots of "int"s (MAX of them),
but in this early C, about all we can do with these structs
is to make pointers to them, or to use their members directly
with the "." operator (e.g., my_object.a[5]).
Like this early C, Standard C *still* has no "array"-valued functions.
This means that there is no need to return an entire array-full of
data, and so if a compiler chooses to use a register for function
return values, it can probably still do -- except, of course, for
those pesky "struct"s. The Standards folks had already decided to
allow structure assignment, so that:
struct X this, that;
...
this = that;
was OK. This meant that there were now "struct" rvalues, but they
generally were just made out of ordinary lvalues. There is a
definite memory location for "this.a[2]" and for "that.a[6]",
because these are ordinary variables.
When the C Standardization folks got around to writing up wording
to allow programmers to return "struct"s from functions, they were
faced with a bit of a problem. Those compilers that *did* allow
struct return values -- there were some by then, otherwise Standard
C probably would not have allowed it at all -- usually did it by
a "hidden pointer" trick. Instead of "struct X f()", they compiled
code for a "struct X *real_f()", or a "void real_f(struct X *)",
or some such. The real, internal version of function f() does, of
course, need to have some place in memory to stick the real structure
it will "return".
Now, if f() really *does* use a hidden pointer in order to "return"
its "struct X", what happens if you somehow get hold of that pointer?
We cannot answer that, because we do not know where this hidden
pointer points, and (for instance) that particular bit of memory
might get overwritten by the next statement. Of course, the Standard
does not have to require that there *be* a hidden pointer; it just
needs to require, and does require, some useful behavior. In this
case, an obvious useful behavior is to say that you (the programmer)
can store the return value in a "struct" you yourself provide, such
as:
my_object = f();
(which may well compile to "real_f(&my_object);", if that will have
the same effect). That is, f() returns an rvalue "struct X". But
this leaves a hole in the usual rules for arrays -- the ones that
say that all arrays are lvalues sitting around in memory, and almost
any time you mention an array lvalue, you get instead a pointer
rvalue, according to that rule given in the FAQ.
If we have an:
int *p;
and we say:
p = &this.a[3];
or even:
p = my_object.a;
there is, in all these cases, some particular bit memory already
allocated somewhere in the machine that we can find, so we can just
have "p" point there. But if we say:
p = f().a; /* oops */
we would have to make "p" point into whatever secret, hidden tricky
memory f() might be using to hold its return value. The ANSI/ISO
C solution is to prohibit writing:
p = f().a; /* oops */
entirely, by saying that the array within a struct-valued function
is *not* an lvalue, in spite of the fact that arrays within every
*other* struct *are* lvalues. This allows every compiler to use
some secret hidden tricky pointer, or some other method entirely,
to return "struct"s, without ever having to give up its secrets.
This rule against finding the address of the array has a sort of
unwanted side effect, though, because "a[i]" is defined in terms
of pointer arithmetic. So, even though the Standard also allows
us to write, say:
int i;
i = f().how_many;
it prohibits an otherwise entirely sensible request like:
i = f().a[1];
and in order to extract any array element, you must first copy
*all* of them to a temporary structure:
struct X temp;
temp = f();
i = temp.a[1];
/* or: i = (temp = f(), temp.a[1]) */
/* or even:
i = (temp = f()).a[1] */
all of which are quite likely, in our hypothetical compiler, to
compile to the same code as you would get for:
real_f(&temp);
i = temp.a[1];
Finally, I think it is worth noting that, with a lot of compilers,
you may actually wind up with more efficient code if you make the
"hidden" pointer explicit as an argument, and write f(&temp)
yourself. The reason has to do with that phrase I snuck in above:
"if that will have the same effect." In general, it is hard
(sometimes equivalent to the halting problem) for the compiler to
optimize f() as well as we might like. For instance, suppose we
have:
struct X f(void) {
struct X ret;
int i;
for (i = 0; i < MAX; i++)
ret.a[i] = something();
return ret;
}
This is often, one way or another, ultimately executed in the
same way as:
void real_f(struct X *hidden) {
struct X ret;
int i;
for (i = 0; i < MAX; i++)
ret.a[i] = something();
memcpy(hidden, &ret, sizeof ret);
}
The memcpy() here is required whenever something() might look at
the original "struct X" being passed into f(). In practise, this
is pretty rare, and it is just wasted work. If you write real_f()
directly, requiring a return-struct pointer as an argument, you
can eliminate the wasted work. (Of course, then *you* become
responsible for deciding whether you can build the return value
"in place" or need to use a temporary.)
--
In-Real-Life: Chris Torek, Berkeley Software Design Inc
El Cerrito, CA Domain: torek@bsdi.com +1 510 234 3167
Antispam notice: unsolicited commercial email will be handled at my
consulting rate; pyramid-scheme mail will be forwarded to the FTC.
Index
Home
About
Blog