Есть моменты, когда я хочу иметь неполную структуру (только один файл C знает о своих членах) поэтому я могу определить API для любых манипуляций и поэтому разработчики не могут легко манипулировать им вне API.
Проблема с этим, это часто означает, что вам нужна функция-конструктор, которая выделяет данные и освобождает их после (используя malloc
и free
).
В некоторых случаях это имеет мало смысла с точки зрения управления памятью, особенно если struct
невелик, а его выделено и освобождено.
Итак, мне было интересно, что может быть переносным способом сохранить члены локально в исходном файле C и по-прежнему использовать распределение стека.
Конечно, это C, если кто-то хочет возиться с внутренними элементами struct
, которые они могут, но я бы хотел, чтобы это предупреждение или ошибка, если это возможно.
Пример простого генератора случайных чисел (для краткости включают только новые/бесплатные методы).
Заголовок: rnd.h
struct RNG;
typedef struct RNG RNG;
struct RNG *rng_new(unsigned int seed);
void rng_free(struct RNG *rng);
Источник: rnd.c
struct RNG {
uint64_t X;
uint64_t Y;
};
RNG *rng_new(unsigned int seed)
{
RNG *rng = malloc(sizeof(*rng));
/* example access */
rng->X = seed;
rng->Y = 1;
return rng;
}
void rng_free(RNG *rng)
{
free(rng);
}
Другой источник: other.c
#include "rnd.h"
void main(void)
{
RND *rnd;
rnd = rnd_new(5);
/* do something */
rnd_free(rnd);
}
Возможные решения
У меня было 2 идеи, как это можно было бы сделать, оба чувствуют себя немного клочьями.
Объявить только размер (в заголовке)
Добавьте эти определения в заголовок.
Заголовок: rnd.h
#define RND_SIZE sizeof(uint64_t[2])
#define RND_STACK_VAR(var) char _##var##_stack[RND_SIZE]; RND *rnd = ((RND *)_##var##_stack)
void rnd_init(RND *rnd, unsigned int seed);
Чтобы обеспечить синхронизацию размеров.
Источник: rnd.c
#include "rnd.h"
struct RNG {
uint64_t X;
uint64_t Y;
};
#define STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr) ? 1 : 2]
/* ensure header is valid */
STATIC_ASSERT(RND_SIZE == sizeof(RNG))
void rng_init(RNG *rng, unsigned int seed)
{
rng->X = seed;
rng->Y = 1;
}
Другой источник: other.c
#include "rnd.h"
void main(void)
{
RND_STACK_VAR(rnd);
rnd_init(rnd, 5);
/* do something */
/* stack mem, no need to free */
}
Сохранение размера в синтаксисе для больших членов struct
может быть проблемой, но для небольшой структуры это не такая проблема.
Условно скрыть элементы struct
(в заголовке)
Использование устаревшего атрибута GCC, однако, если есть более переносимый способ сделать это, это будет хорошо.
Заголовок: rnd.h
#ifdef RND_C_FILE
# define RND_HIDE /* don't hide */
#else
# define RND_HIDE __attribute__((deprecated))
#endif
struct RNG {
uint64_t X RND_HIDE;
uint64_t Y RND_HIDE;
};
Источник: rnd.c
#define RND_C_FILE
#include "rnd.h"
void main(void)
{
RND rnd;
rnd_init(&rnd, 5);
/* do something */
/* stack mem, no need to free */
}
Таким образом, вы можете использовать RND
как регулярную структуру, определенную в стеке, просто не получите доступ к своим членам без какого-либо предупреждения/ошибки. Но его GCC только.