Непрозрачные типы, размещаемые в стеке в C

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

Следовательно, например, внутренние компоненты структур должны оставаться скрытыми, если пользовательская программа не должна знать их. Это действительно хорошая практика, так как содержимое и поведение структуры могут измениться в будущем, не влияя на интерфейс.

Отличным способом достижения этой цели является использование неполных типов.

typedef struct foo opaqueType;

Теперь интерфейс, использующий только указатели на opaqueType может быть построен, без необходимости пользователя программы знать внутреннюю работу struct foo,

Но иногда может потребоваться статическое распределение такой структуры, обычно в стеке, для проблем производительности и фрагментации памяти. Очевидно, с вышеупомянутой конструкцией, opaqueType является неполным, поэтому его размер неизвестен, поэтому он не может быть статически распределен.

Обходной путь - выделить "тип оболочки", такой как:

typedef struct { int faketable[8]; } opaqueType;

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

Это в основном работает. Но в одном случае (GCC 4.4) компилятор жалуется, что он нарушает строгое псевдонимы и генерирует глючный двоичный файл.

Теперь я прочитал множество вещей о строгом псевдониме, так что я теперь понимаю, что это значит.

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

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

Также обратите внимание, что visual, clang и gcc 4.6 и более поздние версии не жалуются и отлично работают с этой конструкцией.

[Изменить] Информационное дополнение:

Согласно тестам, проблема возникает только при следующих обстоятельствах:

  • Частный и публичный тип разные. Я кастую публичный тип в приватный внутри .c файл. Очевидно, не имеет значения, являются ли они частью одного союза. Неважно, содержит ли публичный тип char,
  • Если все операции с закрытым типом просто читаются, проблем нет. Только записи вызывают проблемы.
  • Я также подозреваю, что только функции, которые автоматически встроены, попадают в беду.
  • Проблема возникает только на gcc 4.4 при настройке -O3. -О2 в порядке.

Наконец, моя цель - C90. Может быть, С99, если действительно нет выбора.

3 ответа

Вы можете заставить выравнивание с max_align_t и вы можете избежать строгих проблем с псевдонимами, используя массив char поскольку char явно разрешено псевдоним любого другого типа.

Что-то вроде:

#include <stdint.h>
struct opaque
{
    union
    {
        max_align_t a;
        char b[32]; // or whatever size you need.
    } u;
};

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

ОБНОВЛЕНИЕ: Если вы нацелены на C11, то вы также можете использовать alignas():

#include <stdint.h>
#include <stdalign.h>
struct opaque
{
    alignas(max_align_t) char b[32];
};

Конечно, вы можете заменить max_align_t с любым типом вы считаете целесообразным. Или даже целое число.

ОБНОВЛЕНИЕ № 2:

Тогда использование этого типа в библиотеке будет примерно таким:

void public_function(struct opaque *po)
{
    struct private *pp = (struct private *)po->b;
    //use pp->...
}

Таким образом, поскольку вы вводите указатель на char Вы не нарушаете строгие правила наложения имен.

То, что вы хотите, является своего рода эквивалентом C++ private контроль доступа в C. Как вы знаете, такого эквивалента не существует. Подход, который вы даете, - примерно то, что я бы сделал. Тем не менее, я бы сделал opaqueType непрозрачный для внутренних компонентов, реализующих тип, поэтому я был бы вынужден привести его к реальному типу во внутренних компонентах. Принудительное приведение не должно генерировать упомянутое вами предупреждение.

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

typedef struct opaqueType_raii_callback opqaueType_raii_callback;
struct opaqueType_raii_callback {
    void (*func)(opqaueType_raii_callback *, opqaueType *);
};
extern void opaqueType_raii (opaqueType_raii_callback *);
extern void opaqueType_raii_v (opaqueType_raii_callback *, size_t);


void opaqueType_raii (opaqueType_raii_callback *cb) {
    opaqueType_raii_v(cb, 1);
}

void opqaueType_raii_v (opaqueType_raii_callback *cb, size_t n) {
    opaqueType x[n];
    cb->func(cb, x);
}

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

struct foo_callback_data {
    opaqueType_raii_callback cb;
    int my_data;
    /* other data ... */
};

void foo_callback_function (opaqueType_raii_callback *cb, opaqueType *x) {
    struct foo_callback_data *data = (void *)cb;
    /* use x ... */
}

void foo () {
    struct foo_callback_data data;
    data.cb.func = foo_callback_function;
    opaqueType_raii(&data.cb);
}

Для меня это кажется чем-то, что просто не должно быть сделано.

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

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

Другие вопросы по тегам