IMHO, you should use a raw buffer for input / output. This is by far more portable (and safer) than guessing the way a compiler will order the fields or the structure on each system.
In addition, this would allow you to pack/unpack data without worrying about byte order or memory alignment.
The macros in this example code were extracted from the facil.io framework header:
/** Reads an unaligned network ordered byte stream to a 16 bit number. */
#define fio_str2u16(c) \
((uint16_t)(((uint16_t)(((uint8_t *)(c))[0]) << 8) | \
(uint16_t)(((uint8_t *)(c))[1])))
/** Reads an unaligned network ordered byte stream to a 32 bit number. */
#define fio_str2u32(c) \
((uint32_t)(((uint32_t)(((uint8_t *)(c))[0]) << 24) | \
((uint32_t)(((uint8_t *)(c))[1]) << 16) | \
((uint32_t)(((uint8_t *)(c))[2]) << 8) | \
(uint32_t)(((uint8_t *)(c))[3])))
/** Writes a local 16 bit number to an unaligned buffer in network order. */
#define fio_u2str16(buffer, i) \
do { \
((uint8_t *)(buffer))[0] = ((uint16_t)(i) >> 8) & 0xFF; \
((uint8_t *)(buffer))[1] = ((uint16_t)(i)) & 0xFF; \
} while (0);
/** Writes a local 32 bit number to an unaligned buffer in network order. */
#define fio_u2str32(buffer, i) \
do { \
((uint8_t *)(buffer))[0] = ((uint32_t)(i) >> 24) & 0xFF; \
((uint8_t *)(buffer))[1] = ((uint32_t)(i) >> 16) & 0xFF; \
((uint8_t *)(buffer))[2] = ((uint32_t)(i) >> 8) & 0xFF; \
((uint8_t *)(buffer))[3] = ((uint32_t)(i)) & 0xFF; \
} while (0);
void req_file_read(req_file *d, unsigned char * buffer){
d->byte_count = fio_str2u32(buffer);
d->start_pos = fio_str2u32(buffer + 4);
d->name_len = fio_str2u16(buffer + 8);
}
void req_file_write(unsigned char * buffer, req_file *d){
fio_u2str32(buffer, d->byte_count);
fio_u2str32(buffer + 4, d->start_pos);
fio_u2str16(buffer + 8, d->name_len);
}
This makes it far easier to handle unaligned memory access as well as network byte ordering on any system. The binary based math makes this both portable and space efficient.
EDIT (X-macros)
As per the comments and concerns raised by Lightness Races in Orbit, here's a header file with X-macros that could be used to automatically create X_read
/ X_write
inline functions.
The downside of serialization is that the byte offset for the raw buffer should be provided when declaring the struct using the macros.
In this example, the same header is included a number of times with different results. Also, the read/write functions don't have to be inlined, it's just an example.
Here's the header:
/* note there's NO include guard in the header file */
#ifndef H__FACIL_IO_MACROS
#define H__FACIL_IO_MACROS
/** Reads an unaligned network ordered byte stream to a 16 bit number. */
#define fio_str2u16(c) \
((uint16_t)(((uint16_t)(((uint8_t *)(c))[0]) << 8) | \
(uint16_t)(((uint8_t *)(c))[1])))
/** Reads an unaligned network ordered byte stream to a 32 bit number. */
#define fio_str2u32(c) \
((uint32_t)(((uint32_t)(((uint8_t *)(c))[0]) << 24) | \
((uint32_t)(((uint8_t *)(c))[1]) << 16) | \
((uint32_t)(((uint8_t *)(c))[2]) << 8) | \
(uint32_t)(((uint8_t *)(c))[3])))
/** Writes a local 16 bit number to an unaligned buffer in network order. */
#define fio_u2str16(buffer, i) \
do { \
((uint8_t *)(buffer))[0] = ((uint16_t)(i) >> 8) & 0xFF; \
((uint8_t *)(buffer))[1] = ((uint16_t)(i)) & 0xFF; \
} while (0);
/** Writes a local 32 bit number to an unaligned buffer in network order. */
#define fio_u2str32(buffer, i) \
do { \
((uint8_t *)(buffer))[0] = ((uint32_t)(i) >> 24) & 0xFF; \
((uint8_t *)(buffer))[1] = ((uint32_t)(i) >> 16) & 0xFF; \
((uint8_t *)(buffer))[2] = ((uint32_t)(i) >> 8) & 0xFF; \
((uint8_t *)(buffer))[3] = ((uint32_t)(i)) & 0xFF; \
} while (0);
/* convert SERIAL_STRUCT_NAME to actual name */
#define SERIAL_STRUCT_MAKE(struct_name) SERIAL_STRUCT_MAKE2(struct_name)
#endif
#if SERIALIZE_TYPE /* create the type */
#undef SERIALIZE_TYPE
#undef SERIAL_STRUCT_FIELD
#define SERIAL_STRUCT_FIELD(name, bits, pos) uint##bits##_t name
#undef SERIAL_STRUCT_MAKE2
#define SERIAL_STRUCT_MAKE2(struct_name) \
typedef struct { \
SERIAL_STRUCT_FIELDS; \
} struct_name##_s;
/* perform macros */
SERIAL_STRUCT_MAKE(SERIAL_STRUCT_NAME)
#elif SERIALIZE_READ /* create reader function */
#undef SERIALIZE_READ
#undef SERIAL_STRUCT_FIELD
#define SERIAL_STRUCT_FIELD(name, bits, pos) \
dest->name = fio_str2u##bits((src + (pos)))
#undef SERIAL_STRUCT_MAKE2
#define SERIAL_STRUCT_MAKE2(struct_name) \
inline static void struct_name_read(struct_name##_s *dest, \
unsigned char *src) { \
SERIAL_STRUCT_FIELDS; \
}
/* perform macros */
SERIAL_STRUCT_MAKE(SERIAL_STRUCT_NAME)
#elif SERIALIZE_WRITE /* create writer function */
#undef SERIALIZE_WRITE
#undef SERIAL_STRUCT_FIELD
#define SERIAL_STRUCT_FIELD(name, bits, pos) \
fio_u2str##bits((dest + (pos)), src->name)
#undef SERIAL_STRUCT_MAKE2
#define SERIAL_STRUCT_MAKE2(struct_name) \
inline static void struct_name##_write(unsigned char *dest, \
struct_name##_s *src) { \
SERIAL_STRUCT_FIELDS; \
}
/* perform macros */
SERIAL_STRUCT_MAKE(SERIAL_STRUCT_NAME)
#endif
In the implementation file, the information might look like this (again, the inline approach could be altered):
/* will produce req_file_s as the struct name, but you can change that */
#define SERIAL_STRUCT_NAME req_file
#define SERIAL_STRUCT_FIELDS \
SERIAL_STRUCT_FIELD(start_pos, 32, 0); \
SERIAL_STRUCT_FIELD(byte_count, 32, 4); \
SERIAL_STRUCT_FIELD(name_len, 16, 8)
#define SERIALIZE_TYPE 1
#include "serialize.h"
#define SERIALIZE_READ 1
#include "serialize.h"
#define SERIALIZE_WRITE 1
#include "serialize.h"
This could be adjusted so SERIALIZE_TYPE
also declares the functions (without defining them), and the functions aren't inlined (so only the implementation file includes the header 3 times per type.