Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should all structs that are expected to be read from binary be marked as packed?

I know that some structs, may, or may not, add padding between elements.

My current project is reading input from /dev/input files. The binary layout of these files is defined in <linux/input.h>:

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

I wonder though that this struct is not marked with a packed attribute. Meaning that the /dev/input files (which are packed bit by bit) are not guaranteed to match the same package as the struct. Thus the logic

struct input_event event;
read(fd, &event, sizeof(event));

Is not defined to work across all archs.

Do I have a fallacy in my logic? Or is it safe to assume that somethings are not going to be packed?

like image 355
Dellowar Avatar asked Feb 02 '17 17:02

Dellowar


2 Answers

Packing by Layout

In the current case, you are safe. Your struct input_event is allready layed out packed.

struct timeval time;  /* 8 bytes */
__u16 type;           /* 2 bytes */
__u16 code;           /* 2 bytes */
__s32 value;          /* 4 bytes */

This means, the members form clean 32Bit blocks and so there is no padding required. This article explains how the size of struct members (especially chars) and their layout affect the padding and thus also the final size of a struct.

Packing by the Preprocessor

Packing structs via the preprocessor seems to be a good solution at a first sight. Looking a little closer, there show up several downsides and one of those hits you at the point you are caring about (see also #pragma pack effect)

Performance

Padding assures that your struct members are accessible without searching inside memoryblocks (4byte blocks on a 32bit machine and 8byte blocks on a 64bit machine respectively). In consequence, packing such structures leads to members spanning over multiple memoryblocks and therefore require the machine to search for them.

Different Platforms (Archs, Compilers)

Preprocessor instructions are heavily vendor and architecture specific. Thus, using them leads to lesser or in worst case non-portability of your code.

Conclusion

As the author of this article (already mentioned above) states, even NTP directly reads data from the network into structures. So, carefully laying out your structures and maybe padding them by hand might be the safest and also most portable solution.

like image 159
Simon Schlechten Avatar answered Oct 19 '22 01:10

Simon Schlechten


If you insist on directly loading structures into memory from binary images, then C is not your friend. Padding is allowed, and the basic types can have different widths and endiannness. You are not even guaranteed 8 bits in a byte. However packing the structures and sticking to int32_t and so on will help a great deal, it's effectively portable bar endiannness.

But the best solution is to load the structure from the stream portably. This is even possible with real numbers, though a bit fiddly.

This is how to read a 16 bit integer portably. See my github project for the rest of the functions (similar logic) https://github.com/MalcolmMcLean/ieee754

/**
  Get a 16-bit big-endian signed integer from a stream.
  Does not break, regardless of host integer representation.
  @param[in] fp - pointer to a stream opened for reading in binary mode
  @ returns the 16 bit value as an integer
*/
int fget16be(FILE *fp)
{
    int c1, c2;

    c2 = fgetc(fp);
    c1 = fgetc(fp);

    return ((c2 ^ 128) - 128) * 256 + c1;
}
like image 34
Malcolm McLean Avatar answered Oct 19 '22 00:10

Malcolm McLean