Структура над гибким членом массива - PullRequest
0 голосов
/ 25 июня 2018

Я пишу программу на C (g ++ compilable), которая имеет дело с множеством различных структур, все из буфера с предопределенным форматом. Формат указывает, какой тип структуры я должен загрузить. Это может быть решено с помощью союзов, но огромная разница в размерах структур заставила меня выбрать конструкцию с пустотой * в ней:

struct msg {
    int type;
    void * data; /* may be any of the 50 defined structures: @see type */
};

Проблема в том, что мне нужно 2 malloc звонка и 2 free. Для меня вызовы функций стоят дорого, а malloc дорого. Со стороны пользователей было бы здорово просто free сообщения. Поэтому я изменил определение на:

struct msg {
    int type;
    uint8_t data[]; /* flexible array member */
};
...
struct msg_10 {
    uint32_t flags[4];
    ...
};

Всякий раз, когда мне нужно десериализовать сообщение, я делаю:

struct msg * deserialize_10(uint8_t * buffer, size_t length) {
    struct msg * msg = (struct msg *) malloc(offsetof(struct msg, data) + sizeof(struct msg_10));
    struct msg_10 * payload = (__typeof__(payload))msg->data;

    /* load payload */
    return msg;
}

И чтобы получить член этой структуры:

uint32_t msg10_flags(const struct msg * msg, int i)
{
    return ((struct msg_10 *)(msg->data))->flags[i];
}

С этим изменением gcc (и g ++) выдает красивое сообщение warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing].

Я думаю, что это распространенная проблема (но я не нашел здесь ответа) о том, как каким-то эффективным образом представить семейство сообщений в C.

Я понимаю, почему появилось предупреждение, у меня следующие вопросы:

  1. Возможно ли реализовать что-то подобное без предупреждения или это по своей природе некорректно? (или не является эксклюзивным: P, и я почти убежден, что мне следует провести рефакторинг)
  2. Было бы лучше представлять сообщения, используя что-то вроде следующего кода?

    struct msg {
        int type;
    };
    ...
    struct msg_10 {
        struct msg; /* or int type; */
        uint32_t flags[4];
        ...
    };
    
  3. Если да, предостережения? Могу ли я всегда писать и использовать следующее?

    struct msg * deserialize_10(uint8_t * buffer, size_t length) {
        struct msg_10 * msg = (struct msg_10 *) malloc(sizeof(struct msg_10));
    
        /* load payload */
        return (struct msg *)msg;
    }
    
    uint32_t msg10_flags(const struct msg * msg, int i) {
        const struct msg_10 * msg10 = (const struct msg_10 *) msg;
        return msg10->flags[i];
    }
    
  4. Любой другой?

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

Ответы [ 4 ]

0 голосов
/ 25 июня 2018

Нет malloc, нет освобождения, нет псевдонимов, вызовы функций встроены , для простых или не дополненных естественным образом выровненных структур, встроенные функции эквивалентны memcpy или регистровой копии для небольших простых структур . Для более сложных структур компилятор выполняет всю тяжелую работу.

Поскольку вы десериализуетесь, alignemnet байтов в буфере, скорее всего, упакован и НЕ выровнен естественным образом. Взгляните на исходный код ядра Linux файла pack_struct.h (https://elixir.bootlin.com/linux/v3.8/source/include/linux/unaligned/packed_struct.h)

Вместо u16, u32, u64, разверните функцию для каждого из ваших msg_0..msg_10..msg_ (n-1). Как видно из исходного файла, он созрел для упрощения каждого невыровненного типа и встроенной функции с помощью нескольких простых макросов. Используя ваши примеры имен

struct msg {
    int type;
};
...
struct msg_10 {
    struct msg MsgStruct; /* or int type; */
    uint32_t flags[4];
    ...
};

#define UNALIGNED_STRUCT_NAME(msg_struct_tag) \
    UNA_##msg_struct_tag

#define DECLARE_UNALIGNED_STRUCT(msg_struct_tag) \
  struct UNALIGNED_STRUCT_NAME(msg_struct_tag) \
  {struct msg_struct_tag x;} __attribute__((__packed__))

#define DESERIALIZE_FN_NAME(msg_struct_tag) \
    deserialize_##msg_struct_tag

#define CALL_DESERIALIZE_STRUCT_FN(msg_struct_tag, pbuf) \
    DESERIALIZE_FN_NAME(msg_struct_tag)(pbuf)

#define DEFINE_DESERIALIZE_STRUCT_FN(msg_struct_tag) \
    static inline \
        struct msg_struct_tag DESERIALIZE_FN_NAME(msg_struct_tag)(const void* p) \
    { \
        const struct UNALIGNED_STRUCT_NAME(msg_struct_tag) *ptr = \
            (const struct UNALIGNED_STRUCT_NAME(msg_struct_tag) *)p; \
        return ptr->x; \
    }

...
DECLARE_UNALIGNED_STRUCT(msg_9);
DECLARE_UNALIGNED_STRUCT(msg_10);
DECLARE_UNALIGNED_STRUCT(msg_11);
...
...
DEFINE_DESERIALIZE_STRUCT_FN(msg_9)
DEFINE_DESERIALIZE_STRUCT_FN(msg_10)
DEFINE_DESERIALIZE_STRUCT_FN(msg_11)
...

Для десериализации сообщения 10

struct msg_10 ThisMsg = CALL_DESERIALIZE_STRUCT_FN(msg_10, buffer);

Или десериализовать сообщение 13 в байте 9 в буфере

struct msg_13 OtherMsg = CALL_DESERIALIZE_STRUCT_FN(msg_13, &(buffer[9]));
0 голосов
/ 25 июня 2018

Вы, конечно, можете создать что-то подобное, используя makros;message_header работает как родительская структура для всех типов сообщений.Будучи первым членом таких структур, они имеют один и тот же адрес.Следовательно, после создания msg(int) и приведения его к message_header вы можете освободить его, просто позвонив на него бесплатно.(C ++ работает примерно так же, кстати)

Это то, что вы хотели?

struct message_header {
    int type;
};

#define msg(T) struct {struct message_header header; T data} 

struct message_header* init_msg_int(int a) {
    msg(int)* result = (msg(int)*)(malloc(sizeof(msg(int))));
    result->data = a;
    return (struct message_header*)result;
}

int get_msg_int(struct message_header* msg) {
    return ((msg(int)*)msg)->data;
}

void free_msg(struct message_header* msg) {
    free(msg);
}    
0 голосов
/ 25 июня 2018

Чтобы избежать строгого алиасинга, вы можете обернуть свою структуру внутри объединения. С C11 вы можете использовать анонимную структуру, чтобы избавиться от дополнительного уровня, необходимого для доступа к «флагам»:

typedef union
{
  struct
  {
    uint32_t flags[4];
  };  
  uint8_t bytes[ sizeof(uint32_t[4]) ];
} msg_10;

А теперь вы можете делать msg_10* payload = (msg_10*)msg->data; и получать доступ к payload, не беспокоясь о строгих нарушениях псевдонимов, поскольку тип объединения включает тип (uint8_t[]), совместимый с эффективным типом объекта.

Обратите внимание, что указатель, возвращаемый malloc, не имеет действующего типа, пока вы не получите к нему доступ через указатель на определенный тип. Таким образом, в качестве альтернативы, вы можете быть уверены, что получите доступ к данным с правильным типом после malloc, и это также не приведет к строгому нарушению псевдонимов. Что-то вроде

struct msg_10 * msg = malloc(sizeof(struct msg_10));
struct msg_10 dummy = *msg; 

Там, где dummy не будет использоваться, он просто устанавливает эффективный тип.

0 голосов
/ 25 июня 2018

Я пишу программу на C (g++ компилируемо)

Это недоразумение.

Исходные файлы C должны быть скомпилированы с помощью gcc ( не g++).Исходные файлы C ++ должны быть скомпилированы g++ (а не gcc).Помните, что GCC означает Компилятор Gnu Коллекция (а также содержит gfortran и gccgo и т. Д. ... при соответствующей настройке),Исходные файлы Fortran должны быть скомпилированы с gfortran (при использовании GCC ), исходные файлы Go должны быть скомпилированы с gccgo (при использовании GCC ), код Ada должен быть скомпилированс gnat (при использовании GCC ) и т. д.

Подробнее о Вызов GCC .Проверьте, что происходит, передав также -v вашей команде gcc или g++ (она должна вызывать компилятор cc1, а не cc1plus).

Если вы настаиваете на компиляцииИсходный файл C99 или C11 с g++ (не gcc), который ИМХО неверен и сбивает с толку, обязательно должен пройти хотя бы флаги -std=c99 (или -std=gnu11 и т.д ...) и -x c.

Но вам действительно нужно исправить вашу сборку автоматизацию или процедуру сборки для использования gcc (не g++) для компиляции кода C.Ваша настоящая проблема в том, что эта проблема (какая-то ошибка в Makefile или что-то другое).

Во время соединения используйте g++, если вы смешиваете код C и C ++.

Обратите внимание, что члены гибкого массива не существуют (и никогда не существовали) в C ++, даже в будущем C ++ 20.В C ++ вы можете использовать 0 в качестве объявленного размера, например, код:

#ifdef __cplusplus
#define FLEXIBLE_DIM 0
#else
#define FLEXIBLE_DIM /*empty flexible array member*/
#endif

, а затем объявить:

struct msg {
  int type;
  uint8_t data[FLEXIBLE_DIM]; /* flexible array member */
};

, но это работает только потому, что uint8_t является POD, и ваш g++ компилятор может (иногда) выдавать предупреждения о "переполнении буфера" или "индексе за пределами" (и вы должны никогда не зависеть от времени компиляции sizeof этого data поле).

...