There are some truly weird corner cases with various ABIs. My favourite is the Linux x86-32 ABI, which says that unions are passed as structs and (unlike the *BSD / macOS version of the same ABI) does not use the small structure optimisation. Consider this contrived example function:
union X
{
void *a;
long b;
};
union X ret()
{
return (union X){12};
}
On FreeBSD, this compiles to:
movl $12, %eax
retl
But on Linux, it’s:
movl 4(%esp), %eax
movl $12, (%eax)
retl $4
On FreeBSD, quite sensibly, a word-sized structure is returned in a word-sized register. On Linux, the caller allocates a word-sized place on the stack and then passes a pointer to it into the callee. The callee then stores the value in the pointer.
I ended up rewriting a bunch of code to cast things via uintptr_t (which, in spite of effectively being a union of an integer and a pointer in the C spec, is not treated as one in the ABI) to avoid overhead on Linux. Fortunately, the 64-bit ABI fixed this particular issue.
There are some truly weird corner cases with various ABIs. My favourite is the Linux x86-32 ABI, which says that
union
s are passed asstruct
s and (unlike the *BSD / macOS version of the same ABI) does not use the small structure optimisation. Consider this contrived example function:On FreeBSD, this compiles to:
But on Linux, it’s:
On FreeBSD, quite sensibly, a word-sized structure is returned in a word-sized register. On Linux, the caller allocates a word-sized place on the stack and then passes a pointer to it into the callee. The callee then stores the value in the pointer.
I ended up rewriting a bunch of code to cast things via
uintptr_t
(which, in spite of effectively being a union of an integer and a pointer in the C spec, is not treated as one in the ABI) to avoid overhead on Linux. Fortunately, the 64-bit ABI fixed this particular issue.This is an insightful article that provides useful information also for people that already have some knowledge of Assembly and C++.