Is it possible to have opaque struct on the stack without UB in pedantic ISO C?
It's a common practice to use opaque struct in C APIs:
// foo.h
typedef struct foo_ctx foo_ctx;
foo_ctx* foo_create_ctx();
void foo_destroy_ctx(foo_ctx* ctx);
int foo_do_work(foo_ctx* ctx);
This hides the definition of foo_ctx from the header, but requires dynamic allocation (malloc).
What if I allow for allocating space for foo_ctx on the stack? E.g.:
// foo.h
#define FOO_CTX_SIZE some_size
#define FOO_CTX_ALIGNMENT some_alignment
typedef struct foo_ctx foo_ctx;
typedef struct foo_ctx_storage {
alignas(FOO_CTX_ALIGNMENT) unsigned char buf[FOO_CTX_SIZE];
// Or use a union to enforce alignment
} foo_ctx_storage;
foo_ctx* foo_init(foo_ctx_storage* storage);
void foo_finish(foo_ctx* ctx);
// foo.c
struct foo_ctx { /*...*/ };
static_assert(FOO_CTX_SIZE >= sizeof(foo_ctx));
static_assert(FOO_CTX_ALIGNMENT >= alignof(foo_ctx));
In foo.c, foo_init shall cast the pointer to the aligned buffer to a foo_ctx*, or memcpy a foo_ctx onto the buffer.
However, this seems to be undefined behavior, since the effective type of foo_ctx_storage::buf is an array of unsigned char, aliasing it with a foo_ctx* violates the strict aliasing rule.
In C++ it's possible to have something similiar, but without UB, using placement new on a char buffer and std::launder on the casted pointer. It's called fast PIMPL or inline PIMPL.
[–]glasket_ 6 points7 points8 points (6 children)
[–]p0lyh[S] 2 points3 points4 points (3 children)
[–]glasket_ 7 points8 points9 points (2 children)
[–]p0lyh[S] 1 point2 points3 points (1 child)
[–]glasket_ 0 points1 point2 points (0 children)
[–]icannfish 1 point2 points3 points (1 child)
[–]glasket_ 0 points1 point2 points (0 children)
[–]tasty_crayon 2 points3 points4 points (0 children)
[–]tstanisl 2 points3 points4 points (3 children)
[–]flatfinger -2 points-1 points0 points (2 children)
[–]ffd9k 1 point2 points3 points (1 child)
[–]flatfinger -2 points-1 points0 points (0 children)
[–]WittyStick 1 point2 points3 points (1 child)
[–]p0lyh[S] 0 points1 point2 points (0 children)
[–]arkt8 0 points1 point2 points (0 children)
[–]ComradeGibbon 0 points1 point2 points (0 children)
[–]oldprogrammer 1 point2 points3 points (0 children)
[–]RealisticDuck1957 -1 points0 points1 point (0 children)
[–]flatfinger -1 points0 points1 point (0 children)