[regarding a union of "char" array and bitfields, I wrote:]
Actually, I picked the wrong machine; I now think it *would* work
(depending on desired output) on the SPARC. Ones on which it would
likely fail badly are certain Motorola 680x0 implementations.
Missing semicolons? Sorry.
That, and the names "_1" through "_4" are dubious. (I would have
to consult the C standards to see whether those names are reserved
to users in the structure-member namespace. Instead of using names
whose form requires careful scrutiny of a C standard, why not use
names that are clearly off-limits to the implementor?)
Why? Structure padding? I don't think the struct BITS will be padding bits
inserted. added at the end maybe but that is not of interest here.
For discussion purposes, here is the proposed code, turned into
compilable C:
void pack(unsigned char *out, int in1, int in2, int in3, int in4) {
union {
unsigned char c[3];
struct {
unsigned int one:6, two:6, three:6, four:6;
} bits;
} u;
u.bits.one = in1;
u.bits.two = in2;
u.bits.three = in3;
u.bits.four = in4;
out[0] = u.c[0];
out[1] = u.c[1];
out[2] = u.c[2];
}
Now, suppose we have a fairly typical (in today's terms) machine
with 8-bit "char"s and 4-byte "int"s and 4-byte "storage units".
If you print out the size of the union, you will find that it
requires four bytes, not three.
These four bytes are laid out in memory in the following way,
as required by the C standards:
a) when viewed as array-of-char:
at offset 0: u.c[0] (one byte)
at offset 1: u.c[1] (one byte)
at offset 2: u.c[2] (one byte)
at offset 3: <unnamed> (one byte of padding)
(remember that arrays *must* be contiguous)
b) when viewed as unnamed structure with bitfields:
storage unit at offset 0: <four bytes wide, unspecified layout>
Part (b) is where the problem occurs. The layout of the bitfields
within the storage unit is unspecified. Suppose the compiler
chooses to start at "bit 0" of a 32-bit big-endian word. In this
case, u.bits.one occupies bits 0 through 5 inclusive of that 32-bit
word, which live in the unnamed padding byte at offset 3 in the
array in u.c. u.bits.two occupies bits 6 through 11, which uses
two more bits in the unnamed byte and then four bits in the array
element u.c[3]. (At least one Motorola 68020 C compiler handles
-- or handled -- bitfields this way; I used it in the early Sun
days.)
As it happens, SPARC compilers in big-endian mode (which is to say
most of them) are "encouraged" to allocate bitfields starting at
bit 31 and working down towards bit 0. This means that u.bits.one
occupies bits 31 through 26 inclusive, which live in the array
element u.c[0].
On Intel x86 compilers, the situation is reversed: the usual
convention is to allocate bitfields starting at bit 0 and working
up towards bit 31. As it happens, Intel x86 CPUs are little-endian
-- so this means that u.bits.one occupies bits 0 through 5 inclusive,
which are also bits 0 through 5 of u.c[0]. u.bits.two occupies
bits 6 through 11, which are in bits 6 and 7 of u.c[0] (for the
two low-order bits of u.bits.two) and then bits 0 through 3 of
u.c[1].
Of course, there is nothing but convention to prevent an x86 C
compiler from allocating bits from 31 down to 0, in which case
u.c[0] would not map to any of the bits in u.bits. In any case,
the output on the x86 seems a bit peculiar -- the bits are spread
over the bytes in an interesting manner:
#include <stdio.h>
int main(void) {
unsigned char buf[3];
pack(buf, 0x00, 0x3f, 0x00, 0x00);
printf("result: buf[0..3] = 0x%2.2x%2.2x%2.2x\n",
buf[0], buf[1], buf[2]);
return 0;
}
produces:
result: buf[0..3] = 0xc00f00
Packing the tuple <0x00, 0x03, 0x00, 0x00> results in 0xc00000, so
we see that, indeed, buf[0] contains as its two *uppermost* bits
the two *lowermost* bits of the 2nd value.
(The result on the big-endian SPARC is probably closer to what was
expected.)