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