Поскольку порядок упаковки struct варьируется в зависимости от компилятора и архитектуры, лучше всего использовать вспомогательную функцию для упаковки / распаковки двоичных данных.
Например:
static inline void message1_unpack(uint32_t *fields,
const unsigned char *buffer)
{
const uint64_t data = (((uint64_t)buffer[0]) << 56)
| (((uint64_t)buffer[1]) << 48)
| (((uint64_t)buffer[2]) << 40)
| (((uint64_t)buffer[3]) << 32)
| (((uint64_t)buffer[4]) << 24)
| (((uint64_t)buffer[5]) << 16)
| (((uint64_t)buffer[6]) << 8)
| ((uint64_t)buffer[7]);
fields[0] = data >> 32; /* Bits 32..63 */
fields[1] = (data >> 26) & 0x3F; /* Bits 26..31 */
fields[2] = (data >> 10) & 0xFFFF; /* Bits 10..25 */
fields[3] = (data >> 2) & 0xFF; /* Bits 2..9 */
fields[4] = data & 0x03; /* Bits 0..1 */
}
Примечаниечто поскольку последовательные байты интерпретируются как одно целое число без знака (в порядке байтов с прямым порядком байтов), приведенное выше будет идеально переносимым.
Вместо массива полей вы, конечно, можете использовать структуру;но это не должно иметь никакого сходства со структурой на проводе вообще.Однако, если у вас есть несколько различных структур для распаковки, массив полей (максимальной ширины) обычно оказывается проще и надежнее.
Все здравомыслящие компиляторы прекрасно оптимизируют приведенный выше код.В частности, GCC с -O2
делает очень хорошую работу.
Обратное, упаковывающее те же поля в буфер, очень похоже:
static inline void message1_pack(unsigned char *buffer,
const uint32_t *fields)
{
const uint64_t data = (((uint64_t)(fields[0] )) << 32)
| (((uint64_t)(fields[1] & 0x3F )) << 26)
| (((uint64_t)(fields[2] & 0xFFFF )) << 10)
| (((uint64_t)(fields[3] & 0xFF )) << 2)
| ( (uint64_t)(fields[4] & 0x03 ) );
buffer[0] = data >> 56;
buffer[1] = data >> 48;
buffer[2] = data >> 40;
buffer[3] = data >> 32;
buffer[4] = data >> 24;
buffer[5] = data >> 16;
buffer[6] = data >> 8;
buffer[7] = data;
}
Обратите внимание, что маски определяютдлина поля (0x03
= 0b11 (2 бита), 0x3F
= 0b111111 (16 бит), 0xFF
= 0b11111111 (8 бит), 0xFFFF
= 0b1111111111111111 (16 бит));и величина сдвига зависит от позиции бита младшего значащего бита в каждом поле.
Чтобы убедиться, что такие функции работают, упакуйте, распакуйте, перепакуйте и повторно распакуйте буфер, который должен содержать все нули, кроме одного извсе поля, и убедитесь, что данные остаются правильными в течение двух циклов.Обычно достаточно обнаружить типичные ошибки (неправильные значения сдвига битов, опечатки в масках).
Обратите внимание, что документация будет иметь ключевое значение для обеспечения возможности сопровождения кода.Я бы лично добавил блоки комментариев перед каждой из вышеперечисленных функций, подобно
/* message1_unpack(): Unpack 8-byte message to 5 fields:
field[0]: Foobar. Bits 32..63.
field[1]: Buzz. Bits 26..31.
field[2]: Wahwah. Bits 10..25.
field[3]: Cheez. Bits 2..9.
field[4]: Blop. Bits 0..1.
*/
с полем «имена», отражающим их имена в документации.