Register allocation
This may appear to be a weird answer, but consider these other questions: How many registers are necessary to arrange and call these functions? How many registers to keep alive or spill to memory, to care for intermediate results? What registers are clobbered in general or in this particular case? How much do these questions change on other machines?
These questions need not be answered in general, because C was created to abstract away these machine details and ABI conventions. Yet, C is about generating assembly and binary objects, where these questions are paramount.
Having all this unspecified allows for all sorts of micro optimizations at register allocation level.
And, I suspect, also allows some other tricks at source level, because many compound expressions can be freely (and fully) reordered then is possible, in principle, to examining all combinatorial arrangements of unsequenced subexpressions, to find some combination that uses less or spills less registers in the alternatives.
Divergent incompatible historical implementations
I would also echo the excellent and eloquent points of Eric Lippert and Steve on the comments of the question (and encourage them to elaborate in answers), to point out that the history of C plays a very salient role here.
These details are underspecified on the standard of the language, not to enable unspecified future optimizations, but to accommodate various divergent incompatible historical behaviours in various compilers. Where in one vendor, compiler, or particular machine, one particular optimization is culturally acceptable, desirable and even necessary, in another it was not.
But by the time of language standardization, these incompatibilities were not or could not be worked on, so most of these historical incompatibilities optimizations were left intentionally unspecified.
a() + b() + c() + d() + ... + z()
could evaluate the operands in any permutation. So they could be reordered to optimise cache use if e.g.a()
andz()
both wanted to read from the same area of memory. Another possibility isa() + b() + a()
where duplicating the result ofa()
on the stack might be faster than storing it to a temporary variable and duplicating it later. $\endgroup$x*y+f()
might be better in machine code asf()+x*y
, asx*y
requires a temporary, which if done beforef()
will be live across the call. On the other hand ifx
andy
are not themselves used after this line, then thex*y+f()
would release them both, while requiring a temporary to be live across the call, whereasf()+x*y
would require no temporary butx
andy
both live across the call... $\endgroup$x()
andy()
in your example are indeterminately sequenced: it's either all ofx()
then all ofy()
, or vice versa. Having them be unsequenced would be much more aggressive: the compiler would be allowed to interleave their executions, or execute them in parallel in separate threads, etc. The programmer would have to ensure that this didn't cause any data races, or else the behavior would be undefined. Obviously this would allow for many more optimizations, but also make it very hard to write correct code. $\endgroup$