Портативная и плотная упаковка бит

Oct 20 2020

Предположим , у меня есть три unsigned ints, { a, b, c, d}, который я хочу , чтобы упаковать с нестандартной длины, {9,5,7,11} соответственно. Я хочу создать сетевой пакет ( unsigned char pkt[4]), в который я могу упаковать эти значения и надежно распаковать их на другом компьютере, используя тот же файл заголовка, независимо от порядка байтов.

Все, что я читал об использовании упакованных структур, предполагает, что порядок битов не будет предсказуемым, поэтому об этом не может быть и речи. Таким образом, у меня остаются операции с установкой битов и очисткой битов, но я не уверен, как гарантировать, что порядок байтов не вызовет у меня проблем. Достаточно ли следующего, или у меня возникнут проблемы с порядком байтов aи по dотдельности?

void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *pkt){
    uint32_t pkt_h = ((uint32_t)a & 0x1FF)      // 9 bits
                 | (((uint32_t)b & 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c & 0x3F) << 14) // 7 bits
                 | (((uint32_t)d & 0x7FF) << 21); //11 bits
    *pkt = htonl(pkt_h);
}

void unpack_pkt(uint16_t *a, uint8_t *b, uint8_t *c, uint16_t *d, uint8_t *pkt){
    uint32_t pkt_h = ntohl(*pkt);
    (*a) = pkt_h & 0x1FF;
    (*b) = (pkt_h >> 9) & 0x1F;
    (*c) = (pkt_h >> 14) & 0x3F;
    (*d) = (pkt_h >> 21) & 0x7FF;
}

Если да, какие еще меры я могу предпринять для обеспечения портативности?

Ответы

1 harold Oct 20 2020 at 19:49

Структуры с битовыми полями действительно бесполезны для этой цели, поскольку порядок их полей и даже правила заполнения несовместимы.

у меня возникнут проблемы с порядком байтов aи по dотдельности?

Порядок байтов aи dне имеет значения, их порядок байтов никогда не используется. aи dне интерпретируются повторно как необработанные байты, используются или присваиваются только их целочисленные значения, и в этих случаях порядок следования байтов не учитывается.

Однако есть и другая проблема: uint8_t *pktв сочетании со *pkt = htonl(pkt_h);означает, что сохраняется только младший байт (независимо от того, выполняется ли он на машине с прямым или обратным порядком байтов, потому что это не переинтерпретация, это неявное преобразование). uint8_t *pktсамо по себе нормально, но затем результирующую группу из 4 байтов необходимо скопировать в буфер, на который она указывает, ее нельзя назначить всем за один раз. uint32_t *pktпозволит такое однократное назначение работать без потери данных, но это делает функцию менее удобной в использовании.

Точно так же в unpack_pktнастоящее время используется только один байт данных.

Когда эти проблемы будут исправлены, все должно быть хорошо:

void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *buffer){
    uint32_t pkt_h = ((uint32_t)a & 0x1FF)      // 9 bits
                 | (((uint32_t)b & 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c & 0x3F) << 14) // 7 bits
                 | (((uint32_t)d & 0x7FF) << 21); //11 bits
    uint32_t pkt = htonl(pkt_h);
    memcpy(buffer, &pkt, sizeof(uint32_t));
}

void unpack_pkt(uint16_t *a, uint8_t *b, uint8_t *c, uint16_t *d, uint8_t *buffer){
    uint32_t pkt;
    memcpy(&pkt, buffer, sizeof(uint32_t));
    uint32_t pkt_h = ntohl(pkt);
    (*a) = pkt_h & 0x1FF;
    (*b) = (pkt_h >> 9) & 0x1F;
    (*c) = (pkt_h >> 14) & 0x3F;
    (*d) = (pkt_h >> 21) & 0x7FF;
}

Альтернативой, которая работает, не беспокоясь о порядке байтов в любой момент, является ручное деконструирование uint32_t(вместо условной замены байтов htonlи последующей интерпретации как необработанных байтов), например:

void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *pkt){
    uint32_t pkt_h = ((uint32_t)a & 0x1FF)      // 9 bits
                 | (((uint32_t)b & 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c & 0x3F) << 14) // 7 bits
                 | (((uint32_t)d & 0x7FF) << 21); //11 bits
    // example serializing the bytes in big endian order, regardless of host endianness
    pkt[0] = pkt_h >> 24;
    pkt[1] = pkt_h >> 16;
    pkt[2] = pkt_h >> 8;
    pkt[3] = pkt_h;
}

Оригинальный подход неплох, это просто альтернатива, которую следует рассмотреть. Поскольку ничего не интерпретируется заново, порядок байтов вообще не имеет значения, что может повысить уверенность в правильности кода. Конечно, как недостаток, требуется больше кода, чтобы сделать то же самое. Между прочим, даже несмотря на то, что вручную разобрать uint32_tи сохранить 4 отдельных байта кажется большой работой, GCC может эффективно скомпилировать его в bswap32-битное хранилище. С другой стороны, Clang упускает эту возможность, как и другие компиляторы, так что это не лишено недостатков.

AliMirghasemi Oct 20 2020 at 05:36

для упаковки и упаковки я предлагаю использовать такую ​​структуру

  • помните, что размер структуры отличается на других машинах, таких как 8-битная система и 32-битная система, скомпилируйте одну и ту же структуру с разными размерами, мы называем это заполнением в структуре, поэтому вы можете использовать pack, чтобы убедиться, что размер структуры одинаков в передатчике и приемнике
typedef struct {
    uint8_t A;
    uint8_t B;
    uint8_t C;
    uint8_t D;
} MyPacket;

теперь вы можете передать эту структуру в поток байтов, такой как SerialPort или UART или что-то еще, и в приемнике вы можете упаковать байты вместе

см. следующие функции

void transmitPacket(MyPacket* packet) {
    int len = sizeof(MyPacket);
    uint8_t* pData = (uint8_t*) packet;
    while (len-- > 0) {
        // send bytes 1 by 1
        transmitByte(*pData++);
    }
}

void receivePacket(MyPacket* packet) {
    int len = sizeof(MyPacket);
    uint8_t* pData = (uint8_t*) packet;
    while (len-- > 0) {
        // receive bytes 1 by 1
        *pData++ = receiveByte();
    }
}

помните, что порядок битов в байтах везде одинаков, но вы должны проверить порядок байтов, чтобы убедиться, что пакет не будет пропущен приемником

например, если размер вашего пакета составляет 4 байта, и вы сначала отправляете младший байт, вы должны получить младший байт в приемнике

в вашем коде вы получаете пакет в указателе uint8_t *, но ваш фактический размер пакета равен uint32_t и составляет 4 байта