[original poster wrote]
I forgot to mention, that foo is declared with the attribute regparm(0)
.. which is (afaik) also depending on the implementation of the
compiler, so the stack should be used in any cases.
Indeed, it does depend on the compiler -- but be aware that just
because you force the compiler to pass the structure on its
implementation-specific stack does *not* mean that the structure
will be where you expect it to be.
In particular, I once fixed some (seriously scary) i386-specific
boot code that included, in part:
void f(struct S arg) {
... make changes to arg.fields ...
now call other functions, or even just return
}
plus assembly-language code that carefully called f() with a known
stack pointer and secret arrangements to access the changed values
in arg.fields in those other functions or after f() returned.
This actually worked in versions 1.x and 2.x of the compiler, but
gcc3 would sometimes choose to *copy* the argument "arg" to local
storage before making changes to arg.fields, so that the changes
were no longer being made to the special, known-stack-position
struct. Debugging this was "interesting" because the code would
hang very early in the boot process, long before there was any way
to debug it without an ICE, and making *any* change to the code --
including even a single "write a byte to the display memory to
observe progress" -- changed gcc's internal decisions as to whether
and when to copy the argument. (Thus, this was what is often called
a "Heisenbug", whose behavior changes when you attempt to observe
it.)
The fix for this was simple enough and brings us back on topic. I
changed f() to read:
void f(struct S *arg) {
... make changes to arg->fields ...
now call other functions, or return
}
This did take a whole extra four bytes of stack space, along with
an extra instruction or two in the assembly startup; but why whoever
it was who wrote the original might have been concerned about this
is beyond me since f() not only had its own compiler-generated
(hence unpredictable) stack usage but also called other functions
which were not *that* tightly code-and-stack-space-constrained.
The moral, as it were, is also on-topic: even if you know all about
your compiler, choosing to depend on it may be a mistake, as a
future version of that compiler may change its internals. If there
is a way to write the code so that it *has* to work according to
the language rules, consider using that code instead.
Finally, one last note: The offsets of the various fields of a
structure can be obtained with the offsetof() macro, and will be
in ascending order according to the structure's definition. There
may be gaps for "struct padding" between fields, but the first
field will be at offset 0, the next at an offset no less than
"sizeof" the first field, the next no less than "sizeof" that field
further on, and so on.