Thursday, March 25, 2010

Упаковка структур данных

При манипуляции с данными в C/C++ довольно часто пользуются наложением структуры на массив / приведением типа, если не рассуждать о безопасности этого метода, можно сказать, что довольно часто у начинающих бывает одна маленькая и забавная ошибка, они забывают про выравнивание полей структуры в памяти. Для чего это все делают забавные компиляторы? Очевидно, что для оптимизации работы с памятью, учитывая особенности реализации конкретной аппаратной платформы.

Данную ситуацию можно рассмотреть на пример некоторой реализации протокола обмена данными или же на примере работы с файлами двоичной структуры. Допустим, что у нас есть некоторый протокол, в котором каждое сообщение (пакет) имеет заголовок с полями приведенными ниже:


На С/С++ заголовок сообщения вполне можем представить в виде структуры:
typedef struct {
   unsigned char sg;
   unsigned char type;
   unsigned short len;
   time_t time;
   unsigned char crc;
} msg_header_t;

Не будем рассматривать сообщения имеющие блину тела отличную от нуля, пусть это будет короткое сообщение, состоящее только из заголовка. Мы его формируем, передам в кольцевой буфер и так далее...
msg_header_t *msg;
/* ... */
RingBuffer->Write((unsigned char*)msg, sizeof(msg_header_t));

Все замечательно и мы рассчитываем передать в кольцевой буфер блок длиной в 9 байт (при условии, что time_t у нас 4 байта), однако компиляторы хлебом не корми, дай что-нибудь соптимизировать и в итоге мы получаем 12 байт, как раз из за выравнивания.

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

Решение данной ситуации, это явное указание компилятору, как нужно разместить поля структуры в памяти. Компиляторы С/С++ поддерживаются специальную директиву препроцессора #pragma pack. Применяется это так:
#pragma pack(1)
/* Описываем нашу структуру данных с побайтной упаковкой */
#pragma pack()

Посмотреть как это работает можно на простом примере:
/* msg_header_t1 */
typedef struct {
   unsigned char sg;
   unsigned char type;
   unsigned short len;
   time_t time;
   unsigned char crc;
} msg_header_t1;
/* msg_header_t2 */
#pragma pack(1)
typedef struct {
   unsigned char sg;
   unsigned char type;
   unsigned short len;
   time_t time;
   unsigned char crc;
} msg_header_t2;
#pragma pack()
/* ... */
int main(void) {
   printf("msg_header_t1: %d, msg_header_t2: %d\n",
      sizeof(msg_header_t1),
      sizeof(msg_header_t2));
   return 0;
}


Результат должен выглядеть примерно так:
msg_header_t1: 12, msg_header_t2: 9

0 комментария(ев):