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?
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 *
.