Это хорошая практика, чтобы скрыть определение структуры в C?
Я думаю, что скрытое определение структуры делает код более безопасным, так как вы применяете с помощью компилятора, что ни один член структуры не может быть доступен напрямую. Недостатком является то, что пользователи не могут объявлять переменные типа структуры в стеке, потому что размер структуры неизвестен, хотя иногда желательно избегать использования malloc()
, Это может быть (с частичным успехом) решено с alloca(3)
который присутствует во всех основных реализациях libc, хотя эта функция не соответствует POSIX. Учитывая эти маленькие плюсы и минусы, может ли такой дизайн вообще считаться хорошим?
В lib.h
:
struct foo;
extern size_t foo_size;
int foo_get_bar (struct foo *);
В lib.c
:
struct foo {
int bar;
};
size_t foo_size = sizeof foo;
int foo_get_bar (struct foo *foo)
{
return foo->bar;
}
В example.c
:
#include "lib.h"
int bar(void) {
struct foo *foo = alloca (foo_size);
foo_init (foo);
return foo_get_bar (foo);
}
UPD: обновил вопрос, заявив, что идея использования alloca()
должен иметь возможность объявлять структуру в стеке, скрывая ее определение.
2 ответа
Да, это хорошая практика, чтобы скрыть данные.
Альтернатива alloca(foo_size);
должен объявить выровненный массив символов и выполнить преобразование указателя.
Хотя преобразование указателя не полностью переносимо.
Массив символов должен быть VLA, если размер должен быть переменной, а не постоянной времени компиляции.
extern size_t size;
struct sfoo;
#include <stddef.h>
int main(void) {
unsigned char _Alignas (max_align_t) cptr[size];
// or unsigned char _Alignas (_Complex long double) cptr[size]; // some widest type
struct sfoo *sfooptr = (struct sfoo *) cptr;
Если VLA не желательны / недоступны, объявите размер как константу, например #define foo_N 100
это наверняка будет как минимум столько, сколько нужно.
Функция bar
вызывает неопределенное поведение: структура, на которую указывает foo
неинициализирован.
Если вы собираетесь скрыть детали структуры, предоставьте foo_create()
который выделяет один и инициализирует его и foo_finalize
который освобождает любые ресурсы и освобождает его.
То, что вы предлагаете, можно заставить работать, но оно подвержено ошибкам и не является общим решением.