Aside from the detail of returning the value from a function, this is precisely the subject of Defect Report 222, submitted in 2000 by Clive Feather, and the resolution of that DR seems to pretty clearly answer the question: returning a partially-uninitialized struct
is well-defined (although the values of the uninitialized members may not be used.)
The resolution to the DR clarified that struct
and union
objects do not have trap representations (which was explicitly added to §6.2.6.1/6). Consequently member-by-member copying cannot be used on an architecture in which the individual members might trap. Although, presumably for parsimony, no explicit statement to this effect was added to the standard, footnote 42 (now footnote 51) which previously mentioned the possibility of member-by-member copying was replaced by a much weaker statement indicating that padding bits need not be copied.
The minutes of the WG14 meeting (Toronto, October 2000) are clear (emphasis added):
DR222 - Partially-initialized structures
This DR asks the question of whether or not struct
assignment is well
defined when the source of the assignment is a struct
, some of whose
members have not been given a value. There was consensus that this
should be well defined because of common usage, including the standard-specified structure struct tm
. There was also consensus that if
assignment with some members uninitialized (and thus possibly having a
trap value) was being made well defined, there was little value in
requiring that at least one member had been properly given a value.
Therefore the notion that the value of a struct
or union
as a whole can
have a trap value is being removed.
It's interesting to note that in the above minutes, the committee held that it was not even necessary that a single member of the struct
had been given a value. However, that requirement was later reinstated in some cases, with the resolution to DR338 (see below).
In summary:
If an automatic aggregate object has been at least partially initialized or if its address has been taken (thereby rendering it not suitable for a register
declaration as per §6.3.2.1/2), then lvalue-to-rvalue conversion of that object is well-defined.
Such an object can be assigned to another aggregate object of the same type, possibly after having been returned from a function, without invoking undefined behaviour.
Reading the uninitialized members in the copy is either undefined or indeterminate, depending on whether trap representations are possible. (A read through a pointer to an unsigned narrow character type cannot trap, for example.) But if you write the member before reading it, you're fine.
I don't believe there is any theoretical difference between assignment of union
and struct
objects. Obviously union
s cannot be copied member by member (what would that even mean), and that the fact that some inactive member happens to have a trap representation is irrelevant, even if that member is not aliased by any other element. There's no obvious reason why a struct
should be any different.
Finally, with respect to the exception in §6.3.2.1/2: this was added as a result of the resolution to DR 338. The gist of that DR is that some hardware (IA64) can trap the use of an uninitialized value in a register. C99 does not permit trap representations for unsigned chars. So on such hardware, it might not be possible to maintain an automatic variable in a register without "unnecessarily" initializing the register.
The resolution to DR 338 specifically marks as undefined behaviour the use of uninitialized values in automatic variables which could conceivably be stored in registers (i.e., those whose address has never been taken, as though declared register
), thus permitting the compiler to keep an automatic unsigned char
in a register without worrying about the previous contents of that register.
As a side effect of DR 338, it appears that completely uninitialized automatic struct
s whose address has never been taken cannot undergo lvalue-to-rvalue conversion. I don't know if that side-effect was fully contemplated in the resolution to DR 338, but it does not apply in the case of a partially initialized struct
, as in this question.