I'd like to create an opaque type while still allowing users of the type to instantiate it e.g. on the stack by typing
struct Foo obj;
/*...*/
Foo_init(&obj);
Foo_do_something(&obj, param);
/*...*/
The customary opaque pointer approach doesn't allow the first line of this example. As a workaround I am placing a public but opaque "data" array inside the public header file with a fixed size. This seems to work for a few examples but I'm a bit unsure about two points:
Is it safe to cast the address of the data array as is done in Foo_get_bar and Foo_set_bar? This works okay in my tests but it looks questionable.
If FOO_DATA_SIZE remains fixed is it reasonable to expect ABI compatibility in user code?
main.c (Example user code)
#include <stdio.h>
#include <limits.h>
#include "foo.h"
int main() {
struct Foo foo;
Foo_init(&foo);
Foo_set_bar(&foo, INT_MAX);
int bar = Foo_get_bar(&foo);
printf("Got bar: %d\n", bar);
}
foo.h (Public header)
#pragma once
#include <inttypes.h>
#define FOO_DATA_SIZE (64)
struct Foo {
uint8_t data[FOO_DATA_SIZE];
};
void Foo_init(struct Foo *f);
void Foo_set_bar(struct Foo *f, int barval);
int Foo_get_bar(struct Foo *f);
foo.c (Implementation)
#include "foo.h"
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
typedef int64_t bar_t;
struct Foo_private {
bar_t bar;
};
_Static_assert(sizeof(struct Foo_private) <= FOO_DATA_SIZE,
"FOO_DATA_SIZE is insufficient for struct Foo_private");
void Foo_init(struct Foo *foo) {
struct Foo_private foodata;
foodata.bar = (bar_t)0;
memcpy(foo->data, &foodata, sizeof(struct Foo_private));
}
void Foo_set_bar(struct Foo *foo, int barval) {
struct Foo_private *foodata = (void*)&(foo->data);
foodata->bar = (bar_t)barval;
int stored = (int)foodata->bar;
if (stored != barval) {
fprintf(stderr, "Foo_set_bar(%"PRId64"): warning: bar rounded to %"PRId64"\n",
(int64_t)barval, (int64_t)stored);
}
}
int Foo_get_bar(struct Foo *foo) {
struct Foo_private *foodata = (void*)&(foo->data);
bar_t bar = foodata->bar;
return (int)bar;
}