Портативная и герметичная насадка для долота

#c #network-programming #bit-manipulation

#c #сетевое программирование #манипулирование битами

Вопрос:

Предположим, у меня есть три 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 amp; 0x1FF)      // 9 bits
                 | (((uint32_t)b amp; 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c amp; 0x3F) << 14) // 7 bits
                 | (((uint32_t)d amp; 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 amp; 0x1FF;
    (*b) = (pkt_h >> 9) amp; 0x1F;
    (*c) = (pkt_h >> 14) amp; 0x3F;
    (*d) = (pkt_h >> 21) amp; 0x7FF;
}
  

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

Ответ №1:

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

должен ли я столкнуться с проблемами с 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 amp; 0x1FF)      // 9 bits
                 | (((uint32_t)b amp; 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c amp; 0x3F) << 14) // 7 bits
                 | (((uint32_t)d amp; 0x7FF) << 21); //11 bits
    uint32_t pkt = htonl(pkt_h);
    memcpy(buffer, amp;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(amp;pkt, buffer, sizeof(uint32_t));
    uint32_t pkt_h = ntohl(pkt);
    (*a) = pkt_h amp; 0x1FF;
    (*b) = (pkt_h >> 9) amp; 0x1F;
    (*c) = (pkt_h >> 14) amp; 0x3F;
    (*d) = (pkt_h >> 21) amp; 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 amp; 0x1FF)      // 9 bits
                 | (((uint32_t)b amp; 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c amp; 0x3F) << 14) // 7 bits
                 | (((uint32_t)d amp; 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 может эффективно скомпилировать его в хранилище a bswap и 32bit. С другой стороны, Clang упускает эту возможность, и другие компиляторы также могут, так что это не лишено недостатков.

Комментарии:

1. Большое спасибо! Ваше объяснение и альтернативный метод действительно помогли мне понять, как все это работает действительно хорошо.

Ответ №2:

для упаковки и упаковки я предлагаю использовать структуру, подобную этой

  • помните, что размер структуры отличается на других машинах, таких как 8-битная система и 32-битная система. скомпилируйте ту же структуру с разными размерами, мы называем это заполнением в struct, поэтому вы можете использовать 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 байта

Комментарии:

1. Привет @ali, спасибо за ваш вклад. На самом деле это не решает проблему отправки из системы с малым конечным порядком в систему с большим конечным порядком. Моя установка также включает в себя 32-разрядный компьютер на одном конце и 64-разрядный компьютер на другом конце.

2. если две конечные системы используют разные конечные системы, такие как little-endian и big-endian, мы должны использовать функцию подкачки байтов для обратных переменных uint16_t или uint32_t, но наши переменные uint8_t одинаковы, и помните, что переменные bits всегда хранятся в пакете, таком как uint32_t

3. Как я могу быть уверен, что порядок упаковки упакованной структуры согласован на обоих устройствах? Насколько мне известно, порядок не определен в стандарте и поэтому не переносим. Смотрите Первый ответ в этом сообщении о переполнении стека

4. @MarcianoPreciado в этом ответе не используются битовые поля, поэтому порядок определен, но он также бесполезен для ваших целей, поскольку в нем не рассматривается использование полей необычного размера