Search code examples
cgccstructattributespacked

How can I be sure that this C structure is packed both on 32 bit and 64 bit systems? Is "__attribute__((packed))" always necessary?


I'm trying to build a simple custom application layer protocol, essentially carrying a timestamp, plus some other useful information, in order to perform few network measurements between different Linux systems.

The implementation, in my initial idea, should be as portable as possible between different platforms (x86, ARM, ...), as far as Linux systems are concerned.

In order to manage the header, I've created this structure:

struct myhdr {
    __u8 reserved; // 1 byte
    __u8 ctrl; // 1 byte
    __u16 id; // 2 bytes
    __u16 seq; // 2 bytes
    __u16 len; // 2 bytes
    struct timeval sendtime; // 8 or 16 bytes
};

After which some payload data may or may not (if len=0) be present. Since this data has to be sent over the network, I need, if I'm not wrong, the structure to be packed, without any alignment padding.

My doubt is actually whether this can be considered as already packed or not, mainly due to the presence of struct timeval, to carry the timestamp.

Under 32 bit systems, struct timeval should be 8 bytes. Under 64 bit systems, it should be 16 bytes (just tested this by printing sizeof(struct timeval)).

Under 32 bit systems, is it safe to assume it to be already packed, due to the fact that 1+1+2+2+2 bytes = 8 bytes, which is the size of sendtime? Or will be a padding added in any case in order to align every single field to the last one, which is the largest?

What happens then in 64 bit systems, where the last field is 16 bytes? I think that the structure won't be "packed by layout" anymore, in any case (is this correct?).

Is adding __attribute__((packed)) sufficient and always necessary to ensure that the structure is packed when the code is compiled for different platforms? Are there better solutions?


Solution

  • If you define a wire protocol, you really need to use your own type. To be on the safe side, you should use 64 bits for the seconds from 1970, even if many 32-bit systems still use a 32-bit counter.

    struct timeval comes from a system header and can have basically any size, starting with 8 bytes. I'm sure there are 32-bit systems out there where it has size 12 (64-bit time_t for tv_sec to avoid the Y2038 problem, 32-bit long/suseconds_t for tv_usec). Furthermore, POSIX only requires that struct timeval has certain members. Some systems have explicit fields in their system headers to avoid implicit padding by the compiler, leading to further differences in (hypothetical) packing behavior.

    And while __attribute__ ((pack)) does not apply recursively (so sizeof (p->sendtime) will equal sizeof (struct timeval), it still reduces alignment of the entire struct, including the the sendtime member, to 1, so the member can be misaligned and unsuitable for direct use with functions which expected a struct timeval *.