Непрозрачная структура с гибким элементом массива - PullRequest
0 голосов
/ 22 марта 2019

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

event.h

struct event_t;

и в соответствующем C-файле я хотел бы отсортировать его по псевдониму struct inotify_event. Проблема в том, что struct inotify_event содержит гибкий член массива:

struct inotify_event {
    int      wd;      
    uint32_t mask;    
    uint32_t cookie;  
    uint32_t len;
    char     name[];   
};

Согласно 6.7.2.1(p3) (подчеркните мой):

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

невозможно определить struct event_t как

struct event_t{
    struct inotify_event base; //Non-conforming
};

Так что я могу конвертировать struct event_t * в struct inotify_event *. Поскольку 6.7.2.1(p3) касается только структур, решение, которое я вижу, состоит в том, чтобы переопределить имя тега как

union event_t

, а затем определить его позже как объединение одного элемента.

union event_t{
    struct inotify_event event; //Conforming?
};

Единственное требование, которое Стандарт предъявляет к объединению, которое я обнаружил, состоит в том, что набор членов объединения должен быть непустым 6.2.5(p20) (подчеркните мое):

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

ВОПРОС: Является ли это соответствующим / распространенным способом скрыть детали реализации некоторой конкретной структуры данных через union?

Ответы [ 3 ]

3 голосов
/ 22 марта 2019

Вот как бы я это сделал:

event.h

struct event_t;
event_t *create_event(void);
void free_event(event_t *ev);

event.c

#include "event.h";

event_t *create_event(void)
{
    inotify_event *iev = ...;
    return (event_t *)iev;
}

void free_event(event_t *ev)
{
    inotify_event *iev = (inotify_event *)ev;
    // free the event
}

Однако, если вы хотите сохранить дополнительные данные вместе с событием, тогда:

event.h

struct event_t;
event_t *create_event(void);
void free_event(event_t *ev);

event.c

#include "event.h";

struct event_t
{
    inotify_event *iev;
    // additional data
};

event_t *create_event(void)
{
    inotify_event *iev = ...;
    event_t *ev = malloc(sizeof(event_t));
    ev.iev = iev;
    return ev;
}
void free_event(event_t *ev)
{
    inotify_event *iev = (inotify_event *)ev.iev;
    // free the event (iev) first
    free(ev);
}

Если у вас есть несколько реализаций, которые вам нужно скрыть в event_t, тогда:

enum event_type
{
    EVENT_TYPE_INOTIFY,
    EVENT_TYPE_INOTIFY2,
};
struct event_t
{
    event_type type;
    union {
       inotify_event *iev; // you use this when type == EVENT_TYPE_INOTIFY
       inotify_event2 *iev2; // you use this when type == EVENT_TYPE_INOTIFY2
    }
    // additional data
};
2 голосов
/ 22 марта 2019

На сегодняшний день самый простой метод - вставить это в заголовок event.h:

typedef struct inotify_event event_t;

Это объявляет, что существует тип структуры struct inotify_event и объявляет псевдоним для него event_t. Но это вовсе не определяет содержание struct inotify_event.

Только код реализации в event.c включает определение struct inotify_event из системного заголовка; все остальное не включает этот заголовок и не может получить доступ к элементам event_t, кроме как через определяемый вами API доступа.

Вы можете применить это разделение обязанностей путем проверки кода - или путем проверки с помощью grep, или других подобных методов - чтобы гарантировать, что никакой код, кроме реализации вашего типа события, не использует системный заголовок для inotify_event. И, если вы портируете систему, отличную от Linux, без поддержки inotify, то вы просто предоставляете альтернативный непрозрачный тип структуры вместо struct inotify_event в заголовке event.h.

Это позволяет избежать всех вопросов о том, есть ли гибкие элементы массива в структурах и т. Д .; это все не проблема.


Обратите внимание на вопросы и ответы по поводу Что представляет собой тип, за которым следует _t (подчеркивание t)? . Будьте осторожны при создании своих собственных типов с суффиксом _t ¸ - рассмотрите возможность использования префикса для таких имен типов, который дает вам шанс того, что ваши имена будут отличаться от тех, которые предоставляются системой.

2 голосов
/ 22 марта 2019

Объединение одного элемента не имеет смысла.Цель объединения - служить своего рода полиморфной структурой.Доступ к членам структуры осуществляется по смещению, поэтому невозможно поместить неполную структуру или массив в середину структуры.

Например,

struct foo { int a; int b[]; int c; };

В этом примере невозможно длякомпилятор для определения адреса c, поскольку размер b может изменяться во время выполнения.Но если вы поместите неполный массив в конец, все адреса членов структуры могут быть определены по адресу начала структуры.Имейте в виду, что указатели являются просто адресами, поэтому вы можете иметь любые указатели на любые структуры, и все смещения могут быть определены, но вам придется иметь дело с дополнительными ресурсами alloc / free.

Когда вы создаете объединениеВы говорите компилятору Эй!У меня есть эти члены, зарезервируйте достаточно места для меня, чтобы я мог рассматривать эту переменную как foo или bar .Другими словами, компилятор примет самый большой член объединения, и это будет размер объединения.Обычно объединение используется для представления нескольких типов значений.

typedef union { int integer, float real, char *string } value_type;

Таким образом, вы можете трактовать value_type как int, float или char указатель.Ваш код должен знать, как обращаться с каждым членом, но компилятор позаботится о том, чтобы при выполнении malloc(sizeof value_type) у вас было достаточно места для типов дерева.

Теперь ваша проблема.Вы хотите скрыть детали реализации.Обычно это делается путем объявления типа или структуры не полностью в заголовке и полностью только в ваших объектных файлах.Из-за этого, когда пользователь включает ваш заголовок, вся информация, которую имеет компилятор, равна struct my_struct;.Он не может определить размер my_struct, поэтому вы не можете выделить его как malloc(sizeof struct my_struct).Кроме того, поскольку у пользователя нет определений членов, он не может испортить внутреннюю структуру struct.

Работая так, вам нужно будет предоставить пользователю функции для выделения и освобождения my_struct, например struct my_struct *foo = my_struct_new() и my_struct_destroy(foo).

Вы уже делаете это.Чтобы справиться с проблемой struct inotify, я бы сделал один из них.

(1) ОС Surround, специфичная для #ifdef для этой ОС, так что event_t имеет только правильных членов, определенных в зависимости отоперационная система.Вам понадобится #ifdef для ваших функций.Это дает преимущество, заключающееся в том, что в конечном двоичном коде остается бесполезный код, поэтому занимает меньше места.

(2) Имейте указатели на структуры, специфичные для ОС, и позволяйте среде выполнения решать, что делать.Это легче поддерживать.

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