There are times when I want to have a struct which is incomplete (only a single C file knows about its members), so I can define an API for any manipulations and so developers can't easily manipulate it outside the API.
The problem with doing this, its it often means you need a constructor function, which allocates the data, and free it after (using malloc
and free
).
In some cases this makes very little sense from a memory management perspective, especially if the struct
is small, and its allocated and freed a lot.
So I was wondering what might be a portable way to keep the members local to the C source file, and still use stack allocation.
Of course this is C, if someone wants to mess with the struct
internals they can, but I would like it to warn or error if possible.
Example, of a simple random number generator (only include new/free methods for brevity).
Header: rnd.h
struct RNG;
typedef struct RNG RNG;
struct RNG *rng_new(unsigned int seed);
void rng_free(struct RNG *rng);
Source: 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 source: other.c
#include "rnd.h"
void main(void)
{
RND *rnd;
rnd = rnd_new(5);
/* do something */
rnd_free(rnd);
}
Possible solutions
I had 2 ideas how it could be done, both feel a bit of a kludge.
Declare the size only (in the header)
Add these defines to the header.
Header: 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);
To ensure the sizes are in sync.
Source: 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 source: other.c
#include "rnd.h"
void main(void)
{
RND_STACK_VAR(rnd);
rnd_init(rnd, 5);
/* do something */
/* stack mem, no need to free */
}
Keeping the size in sync for large struct
members may be a hassle, but for small struct's it's not such a problem.
Conditionally hide the struct
members (in the header)
Using GCC's deprecated attribute, however if there is some more portable way to do this it would be good.
Header: 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;
};
Source: 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 */
}
This way you can use RND
as a regular struct defined on the stack, just not access its members without some warning/error. But its GCC only.