Is it UB to return a struct without initializing it, if the only subsequent use is in an initialization statement as shown below:
typedef struct { int x; } s;
s callee(void) {
s ret;
return ret;
}
void caller() {
s dummy = callee();
}
Is it UB to return a struct without initializing it, if the only subsequent use is in an initialization statement as shown below:
typedef struct { int x; } s;
s callee(void) {
s ret;
return ret;
}
void caller() {
s dummy = callee();
}
Consider this similar code first:
s ret;
s dummy = ret;
A struct cannot have a trap representation (C11 6.2.6.1/6). But this code causes undefined behaviour due to the Itanium Clause (C11 6.3.2.1/2) which says that using the value of an uninitialized automatic object that never has its address taken causes UB.
So this code would be well-defined:
s ret;
&ret;
s dummy = ret;
For further reading on that clause see: Is a^a or a-a undefined behaviour if a is not initialized?.
For the version with a function return value: it's not spelled out in the standard whether the return value counts as an automatic object for the purposes of the Itanium Clause. I would tend to say it does not, since the return value is not described as an object by the Standard. But it would be good if someone familiar with the Itanium ABI could comment on whether passing an uninitialized struct through a return value triggers a NaT exception.
In lieu of that, my position is that the function call version has the same semantics as the assignment version discussed above, i.e. the posted code is UB but adding &ret;
makes it well-defined.
TLDR: While the ability for functions to "pass through" indeterminate values that callers end up ignoring offers benefits which on most platforms far exceed the cost, and should thus be provided by quality implementations targeting such platforms, the Standard does not require implementations to provide it, and thus "clever" implementations do not.
There are many constructs which all compilers would have handled consistently when the Standard was written, but which the Standard does not explicitly define. The Standard notes that a common way of processing actions where the Standard would impose no requirements is "in a documented manner characteristic of the environment" but in the rationale notes that the decision of when to do so is Quality-of-Implementation issue rather than a conformance one. The rationale also recognizes that an implementation may be conforming while being of such poor quality as to be useless, but that the authors do not think it necessary to expend effort forbidding such implementations.
There are two sensible and useful ways a quality compiler for a typical platform could process code like the above:
A compiler could trap in an implementation-defined means if a function tries to return an uninitialized object, without regard for whether the calling code would make use of the value [the code to generate the trap may have no way of knowing anything about the calling code].
A compiler could leave some arbitrary collection of bits someplace which the caller may or may not use, with no side-effect if the caller doesn't actually use them. Note that if objects of a certain type wouldn't have padding bits when stored in memory, they may have padding bits when stored in registers, and might behave oddly if those padding bits aren't set correctly.
The authors of the Standard made no attempt to enumerate all of the things an implementation might do when dealing with indeterminate values, since they figured that implementers seeking to produce quality implementations would decide what course of action would be most appropriate given the intended platforms and purposes of the implementations in question.
Unfortunately, even though on any common platform it would cost essentially nothing to let functions safely return indeterminate values in cases where the callers are going to ignore them, and in some cases doing that would yield more efficient code than would otherwise be possible (e.g. saying that given:
extern volatile int vv1, vv2, vv3;
int foo(int mode)
{
int result;
vv1 = 1;
if (mode & 1)
result = vv2;
if (mode & 2)
result = vv3;
return result;
}
the statement "foo(0);" would store 1 to vv1 with no other side-effects would let avoid the need to have programmers force compilers to generate an unnecessary load) it has become fashionable for compiler writers to find "clever" ways of exploiting the fact that the Standard doesn't require such guarantees. For example, the above code might be "optimized" to:
int foo(int mode)
{
vv1 = 1;
if (!(mode & 2))
return vv2;
if (mode & 1)
+vv2; // Access and ignore value
return vv3;
}
Whether or not the practical value of such "optimizations" would ever exceed that of letting programmers allow compilers avoid unnecessary loads, programmers who can't be sure their code will only be run on quality implementations will need to make allowances for "clever" ones.