ИМХО, вы должны использовать необработанный буфер для ввода / вывода.Это гораздо более переносимо (и безопаснее), чем угадывать, как компилятор упорядочит поля или структуру в каждой системе.
Кроме того, это позволит вам упаковать / распаковать данные, не беспокоясь о порядке байтов.или выравнивание памяти.
Макросы в этом примере кода были извлечены из заголовка фреймворка facil.io :
/** 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);
}
Это значительно упрощает обработку невыровненной памятидоступ, а также порядок байтов в сети в любой системе.Бинарная математика делает это переносимым и эффективным с точки зрения пространства.
РЕДАКТИРОВАТЬ (X-макросы)
Что касается комментариев и опасений, высказанных гонками легкости на орбите, вот файл заголовка с X-макросы, которые можно использовать для автоматического создания встроенных функций X_read
/ X_write
.
Недостаток сериализации заключается в том, что при объявлении структуры с помощью макросов следует указывать смещение в байтах для необработанного буфера.
В этом примере один и тот же заголовок включен несколько раз с разными результатами.Кроме того, функции чтения / записи не должны быть встроены, это всего лишь пример.
Вот заголовок:
/* 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
В файле реализации информация может выглядеть следующим образом(опять же, встроенный подход может быть изменен):
/* 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"
Это можно настроить так, чтобы SERIALIZE_TYPE
также объявлял функции (не определяя их), и функции не были встроены (так что только реализацияфайл включает заголовок 3 раза для каждого типа.