cochran_emc.h: packed structs
Jef Driesen
jef at libdivecomputer.org
Tue Oct 28 12:26:21 PDT 2014
On 28-10-14 17:32, Dirk Hohndel wrote:
> The reason so many people reverse engineering want packed structures is
> that it makes it so much easier to write readable code.
>
> A typical 8 byte sample might look like this:
>
> struct sample {
> unsigned char temp; // in F
> unsigned int16_t depth; // in 1/16 ft
> unsigned int16_t deco : 1;
> unsigned int16_t tank_idx : 2;
> unsigned int16_t marker : 1;
> unsigned int16_t ascend_warn : 1;
> unsigned int16_t compass : 9;
> unsigned int16_t pressure; // in psi
> unsigned char flags;
> }
>
> I made this up - it's much simpler than some real structures actually are.
>
> The goal is to allow me to say
>
> if (sample->deco) { ... }
>
> instead of
>
> if (data[3] & 0x01) { ... }
>
> and
>
> show_direction(sample.compass)
>
> instead of
>
> show_direction((array_16_le(data + 3) >> 5) & 0x1f)
Such a structure is indeed very convenient to write readable code. But using it
for parsing, in the sense that you overlay the structure of the raw data, is
nearly impossible. At least not in a portable way.
First you have the bitfields. This is major trouble, because the order of the
bits is implementation defined. To make this work, you need different structures
for each compiler. And if you try to use yet another compiler, things may break
again. So forget about using bitfields for portable code.
Next, you have endianness issues for anything that's larger than an 8 bit
integer. To work around these, you can use a small two byte array for a 16bit
integer, and then do array_uint16_{be,le} to get the 16 bit value in a portable
way. But that means you already lost the ability to just use the field directly.
So much of the benefit of the struct is already lost here.
Finally, you have the alignment and padding issues. I'm not 100% sure about
this, but I think that even for structures only containing 8bit integers, the
compiler is allowed to insert padding bytes. Maybe in practice no sane compiler
does this, but do you really want to rely on this? These are the kind of bugs
that are really nasty to discover. We can request the compiler to pack the
structure, but every compiler has it's own dialect for doing that. Not really
nice if you want to write portable code.
So basically the only way to parse in a portable way is by doing something like
this:
value = array_uint16_le(data + offset);
That's what I do in libdivecomputer. Works on every compiler, without having to
resort to ugly hacks or special tricks. To make it more readable, you can of
course define some nice constants for the offsets.
There is also another alternative. When using structures as the in-memory
representation, none of the above problems applies, as long as you fill in the
contents of the struct using the portable method:
sample->deco = data[3] & 0x01;
and from there on, you can use your nice struct:
if (sample->deco) { ... }
Jef
More information about the subsurface
mailing list