(I realize these are a bit old, but I have been away for some time...)
[Given a function l_SYM_2B() that returns a struct that contains
an array...]
Adam said:
The (l_SYM_2B(&a).o[0]) operand appears to be the result of a [] operator.
So the result is evaluated as if the & operator were removed and the []
operator were changed to a + operator.
I found one key difference between the C89/C90 and C99 that must be at
play here.
The property of "being a non-lvalue" propagates from the left side of
'.' operator to its result in both C89/90 and C99. This means that the
following expression
l_SYM_2B(&a).o
is not an lvalue in both C89/90 and C99.
Now, the key moment: in C89/C90 the array-to-pointer conversion was only
applicable to _lvalues_ of array type (see 6.2.2.1). In C99 this
limitation was removed (see 6.3.2.1/3), and now the array-to-pointer
conversion is applicable to any values of array type.
This means that the following expression
l_SYM_2B(&a).o[0]
is ill-formed in C89/90 (there's no way to apply operator '[]' to the
result of previous expression) and well-formed in C99.
Right.
This is where "The Rule" is illustrative. I always say that The
Rule describes how array objects are treated in a value context:
In a value context, an object of type "array N of T" is converted
to a value of type "pointer to T", pointing to the first element
of that array, i.e., the one with subscript 0.
When we apply The Rule in typical C code, we find that arrays never
survive to the "value" stage: arrays are only ever objects, and
when you attempt to do anything with their value, you get instead
a pointer value, stripping off the "array N of" part.
Functions that return a "struct" that contains an array create a
problem. A function's return value is, by definition, a value,
not an object.
C handles values of type "struct S" a bit clumsily, but
consistently: they exist, you can put them on the right hand side
of assignments, you can pass them to functions, and so on:
struct S a, b;
struct T x;
...
a = b; /* valid, copies all the fields of b to a */
x = b; /* ERROR: type mismatch; diagnostic required */
f(b); /* valid, passes the value of the entire struct */
And of course, you can return them from functions as well:
struct S sfunc(void);
b = sfunc();
(Many compilers actually implement this by passing a "secret"
argument to the function, &b in this case, to let the function fill
in the provided struct -- but this is an implementation detail.
Note that in this case, if the return value is discarded, the
compiler has to pass a pointer anyway. Typically this is a pointer
to a compiler-created temporary that is then discarded.)
Because of The Rule, this "works as a value" does not hold for
arrays -- you put an array object in a value context and get a
pointer:
int arr[10];
int *p;
p = a; /* valid, sets p to point to &a[0]; array NOT copied */
g(a); /* valid, passes &a[0] to g(); array NOT copied */
and, by fiat, C simply forbids functions from returning arrays, so
that there is no problem with "function returning array N of T".
But if a function returns a struct and the struct contains an array,
we have the impossible: an array value. Logically, the value is
the value of the entire array -- all the elements at once, as it
were.
Because C applies The Rule to all *other* cases, it "wants", in an
odd nonsentient fashion, to apply it here as well -- but The Rule
converts an array object to a pointer to that object's first element,
and we do not *have* an object to point into. (A number of C89
compilers have bugs here, and apply The Rule anyway, giving you a
pointer to -- well, something. Whether that "something" lasts even
until the next expression is tough to predict, at least not without
digging into the compiler innards or experimenting with disassembling
the compiler's output. But this *is* a bug; it comes about because
the compiler fails to enforce the "object" part of The Rule. After
all, the compiler writer probably forgot that array values exist
at all -- they only occur in this one special case, of a function
returning a struct containing an array.)
For example, the
following simple code will be rejected by Comeau Online compiler in
C89/C90 mode and accepted in C99 mode for this very reason
struct S { int i[5]; };
struct S foo() { struct S s = { 0 }; return s; }
int main() { int i = foo().i[1]; }
Note, that this code does not involve operator '&' at all.
C99 once again solves the "what does it mean to get a pointer to
the nonexistent object" problem by fiat, restricting what you can
do: "i = foo().i[index]" is OK; "p = foo().i" is not.
However, this also means that you original code (with intermediate
variable 'val1') was also ill-formed in C89/C90. But you said that you
could compile it without any problems. Apparently, some quirks of
concrete compilers are also at play here.
Indeed. In general, it compiles (because, unlike the Comeau
compiler, the compiler fails to check the "must be an object" part)
and then produces unreliable machine code, often in weird and
unpredictable fashions (e.g., change the declaration order of local
variables and it sometimes works).