Я должен не согласиться со многими ответами здесь. Я настоятельно рекомендую вам избежать соблазна наложить структуру на входящие данные. Это кажется убедительным и может даже работать на вашей текущей цели, но если код когда-либо будет перенесен на другую цель / среду / компилятор, у вас возникнут проблемы. Несколько причин:
Endianness : архитектура, которую вы используете прямо сейчас, может быть с прямым порядком байтов, но ваша следующая цель может быть с прямым порядком байтов. Или наоборот. Вы можете преодолеть это с помощью макросов (например, ntoh и hton), но это дополнительная работа, и вы должны обязательно вызывать эти макросы каждый раз, когда ссылаетесь на поле.
Выравнивание : используемая вами архитектура может загружать многобайтовые слова со смещением с нечетным адресом, но многие архитектуры не могут. Если 4-байтовое слово пересекает 4-байтовую границу выравнивания, загрузка может вытянуть мусор. Даже если в самом протоколе нет выровненных слов, иногда сам поток байтов не выровнен. (Например, хотя определение заголовка IP помещает все 4-байтовые слова на 4-байтовые границы, часто заголовок ethernet сам толкает IP-заголовок на 2-байтовую границу.)
Заполнение : Ваш компилятор может решить плотно упаковать вашу структуру без заполнения, или он может вставить заполнение, чтобы справиться с ограничениями выравнивания цели. Я видел это изменение между двумя версиями одного и того же компилятора. Вы можете использовать #pragmas, чтобы вызвать проблему, но #pragmas, конечно, зависит от компилятора.
Порядок следования битов : Порядок следования битов внутри битовых полей Си зависит от компилятора. Кроме того, эти биты трудно "получить" для вашего кода времени выполнения. Каждый раз, когда вы ссылаетесь на битовое поле внутри структуры, компилятор должен использовать набор операций маска / сдвиг. Конечно, в какой-то момент вам придется делать это маскирование / смещение, но лучше не делать этого при каждом обращении, если скорость вызывает беспокойство. (Если главным является проблема пробела, используйте битовые поля, но действуйте осторожно.)
Все это не означает "не используйте структуры". Мой любимый подход состоит в том, чтобы объявить дружественную структуру с прямым порядком байтов всех соответствующих данных протокола без каких-либо битовых полей и без учета проблем, а затем написать набор симметричных подпрограмм pack / parse, которые используют структуру в качестве посредника. *
typedef struct _MyProtocolData
{
Bool myBitA; // Using a "Bool" type wastes a lot of space, but it's fast.
Bool myBitB;
Word32 myWord; // You have a list of base types like Word32, right?
} MyProtocolData;
Void myProtocolParse(const Byte *pProtocol, MyProtocolData *pData)
{
// Somewhere, your code has to pick out the bits. Best to just do it one place.
pData->myBitA = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_A_MASK >> MY_BIT_A_SHIFT;
pData->myBitB = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_B_MASK >> MY_BIT_B_SHIFT;
// Endianness and Alignment issues go away when you fetch byte-at-a-time.
// Here, I'm assuming the protocol is big-endian.
// You could also write a library of "word fetchers" for different sizes and endiannesses.
pData->myWord = *(pProtocol + MY_WORD_OFFSET + 0) << 24;
pData->myWord += *(pProtocol + MY_WORD_OFFSET + 1) << 16;
pData->myWord += *(pProtocol + MY_WORD_OFFSET + 2) << 8;
pData->myWord += *(pProtocol + MY_WORD_OFFSET + 3);
// You could return something useful, like the end of the protocol or an error code.
}
Void myProtocolPack(const MyProtocolData *pData, Byte *pProtocol)
{
// Exercise for the reader! :)
}
Теперь остальная часть вашего кода просто манипулирует данными внутри дружественных, быстрых структурных объектов и вызывает пакет / анализ только тогда, когда вам нужно взаимодействовать с потоком байтов. Нет необходимости в ntoh или hton, и нет битовых полей для замедления вашего кода.