Как размотки, описанной в комментарии, вы должны вместо сериализации (при записи) и десериализации (по чтению) комментариев структуры в/из байтового буфера.
Существует несколько способов сделать это. Например, встроенные функции (C99 static inline
), макросы препроцессора, отдельные функции для каждого поля, общая функция для бит-пакетов и т. Д.
Наиболее распространенным вариантом является упаковка и распаковка байт-массивов из/в внутренние структуры. Например, для внутренне используемой структуры
struct mbxh {
INT8U channel:4;
INT8U priority:4;
INT16U length;
INT16U address;
INT8U array[4];
};
static void pack_mbxh(unsigned char *const dst, const struct mbxh *src)
{
dst[0] = src->channel | ((src->priority) << 4);
dst[1] = src->length >> 8;
dst[2] = src->length;
dst[3] = src->address >> 8;
dst[4] = src->address;
dst[5] = src->array[0];
dst[6] = src->array[1];
dst[7] = src->array[2];
dst[8] = src->array[3];
}
static void unpack_mbxh(struct mbxh *dst, const unsigned char *const src)
{
dst->channel = src[0] & 15U;
dst->priority = (src[0] >> 4) & 15U;
dst->length = (src[1] << 8) | src[2];
dst->address = (src[3] << 8) | src[4];
dst->array[0] = src[5];
dst->array[1] = src[6];
dst->array[2] = src[7];
dst->array[3] = src[8];
}
Это особенно полезно, потому что это делает его тривиальным, чтобы указать порядок следования байтов; в приведенном выше примере используется порядок байтов или байтов сети для полей length
и address
.
Если целевая система очень ограничена оперативной памятью, использование макросов препроцессора для прямого доступа к «упакованным» полям часто является хорошим вариантом. Это использует меньше памяти, но больше ресурсов ЦП. (Обратите внимание, что «упакованные» поля используют тупоконечник или сетевой порядок байт здесь тоже.)
#define mbxh_get_channel(data) ((data)[0] & 15U)
#define mbxh_get_priority(data) ((data)[0] >> 4)
#define mbxh_get_length(data) ((((INT16U)(data)[1]) << 8) | ((INT16U)(data)[2]))
#define mbxh_get_address(data) ((((INT16U)(data)[3]) << 8) | ((INT16U)(data)[4]))
#define mbxh_get_array(data, i) ((data)[i])
#define mbxh_set_channel(data, value) \
do { \
(data)[0] = ((data)[0] & 240U) | ((INT8U)(value)) & 15U); \
} while (0)
#define mbxh_set_priority(data, value) \
do { \
(data)[0] = ((data)[0] & 15U) | (((INT8U)(value)) & 15U) << 4); \
} while (0)
#define mbxh_set_length(data, value) \
do { \
(data)[1] = ((INT16U)(value)) >> 8; \
(data)[2] = (INT8U)(value); \
} while (0)
#define mbxh_set_address(data, value) \
do { \
(data)[3] = ((INT16U)(value)) >> 8; \
(data)[4] = (INT8U)(value); \
} while (0)
#define mbxh_set_array(data, index, value) \
do { \
(data)[(index)] = (INT8U)(value); \
} while (0)
На практике, особенно если у вас есть много таких структур, сочетание из них будет работать. Во-первых, написать некоторые компактные функции для доступа к каждому типа поля: низкий откусывание, высокий полубайт, или 16-битное поле,
static INT8U get4u_lo(const INT8U *const ptr)
{
return (*ptr) & 15U;
}
static INT8U get4u_hi(const INT8U *const ptr)
{
return (*ptr) >> 4;
}
static INT16U get16u(const INT8U *const ptr)
{
return (((INT16U)ptr[0]) << 8) | ptr[1];
}
static void set4u_lo(INT8U *const ptr, INT8U val)
{
*ptr &= 240U;
*ptr |= val & 15U;
}
static void set4u_hi(INT8U *const ptr, INT8U val)
{
*ptr &= 15U;
*ptr |= (val % 15U) << 4;
}
static void set16u(INT8U *const ptr, INT16U val)
{
ptr[0] = val >> 8;
ptr[1] = val;
}
Далее, вы написать полевые аксессоров за структуры с использованием выше вспомогательные функции:
#define mbxh_get_channel(data) get4u_lo((INT8U *)(data)+0)
#define mbxh_get_priority(data) get4u_hi((INT8U *)(data)+0)
#define mbxh_get_length(data) get16u((INT8U *)(data)+1)
#define mbxh_get_address(data) get16u((INT8U *)(data)+3)
#define mbxh_get_array(data, i) ((data)[5+(i)])
#define mbxh_set_channel(data, v) set4u_lo((INT8U *)(data)+0, (v))
#define mbxh_set_priority(data, v) set4u_hi((INT8U *)(data)+0, (v))
#define mbxh_set_length(data, v) set16u((INT8U *)(data)+1, (v))
#define mbxh_set_address(data, v) set16u((INT8U *)(data)+3, (v))
#define mbxh_set_array(data, i, v) ((data)[5+(i)] = (v))
Как и во всех примерах, приведенных в этом ответе, выше тоже использовать тупоконечника или сетевой порядок байтов данных. channel
находится в четырех младших разрядах и priority
в четырех старших разрядах первого байта данных.
В целом, я рекомендую первый вариант (преобразование структуры для вызова функции) для настольных приложений и для случаев, когда вы используете внутреннюю структуру. Для микроконтроллеров и других случаев с ограниченной памятью я рекомендую эту последнюю.
(Ни один из приведенной выше коды не проверяются. Если вы обнаружили опечатку или ошибку или другие ошибки, пожалуйста, сообщите мне в комментариях, так что я могу исправить пример код выше.)
Использование битовых полей в структуре, которая вам планировать писать дословно в сети - это, пожалуй, самый худший возможный случай использования. Вы должны написать код для сериализации/де-сериализации, чтобы вы могли контролировать каждый байт и не зависели от того, что делает компилятор. – unwind
Существует никогда (или всегда) дополнение битов. Заполнение относится только к байтам. Битовые поля занимают целый ряд байтов, например, 'channel' - один байт. –
@unwind - Это правильно. Я хотел бы сериализовать/де-сериализовать. Но как это сделать в общем случае, если существует 10 различных структур разных размеров и переменных? Можете ли вы помочь мне подумать об альтернативе? Все ответы до сих пор являются специфическими для этой структуры ... Но получение универсальной работы для любой структуры - это то, о чем я не могу думать (ограничение моих мозгов). –