If you come up with version 2.0 of the "public.h" header and it includes
a new structure in the union, that structure could affect the alignment
requirements of the union.
If the only code that allocates such union objects is aware of the
change, the alignment requirements should still satisfy the old union
version, as long as you don't remove the old struct members, right?
I wouldn't expect that other users' code would care... Their foos and
bars are still aligned, and 'foo_t' and 'bar_t' still has the same tag
and same definition[n1256.pdf:6.2.7p1]. I'd view the use of a "newer"
union rather than the "outdated" union as "type-punning", under these
circumstances. I could be mistaken.
Of course, since union version 2.0 could also change the size, hopefully
your old library users aren't using arrays with elements of this union
type! Then they'd be borked.
If an application would define an array, that shouldn't cause any
problems because everywhere in that application they will be using one
version of the union. It's only when you have to pass them between the
application and the library that there can be a mismatch in version.
But in my case the data only needs to be passed from the library to the
application, and never the other way around. And I will be passing
pointers, not the structs or unions directly. The reason for the
pointers is exactly the ability to add new structs in the future without
breaking backwards compatibility, which is not possible with a plain
union. Using the union would be for convenience, to avoid the casting of
a void pointer to the appropriate struct.
I'd figured that you'd meant that.
Hopefully a user doesn't do something like make a copy of a 'foobar_t';
they'd be using an outdated size if you changed it library-wise. And
yet fortunately, since they only know about 'foo_t' and 'bar_t', they'd
still copy as many bytes as they care about.
Along the lines of the suggestion of "Columbus sailed the ocean China
Blue," you could certainly pass a pointer to a common, first member.
This strategy is useful with such macros as 'container_of()' or
'CONTAINING_RECORD()' and I've used and seen that strategy used quite a bit.
A useful first member of 'foo_t' and 'bar_t' might be (but is obviously
not limited to being):
- A 'size_t' with the intention of expressing the size of the object
(potentially even useful as a make-shift signature if you can guarantee
all 'XXX_t' will have different sizes).
- Your original 'enum' value for distinguishing the type (you had
'foobar_type_t' for this). Woe be to you if the day comes that you
think the order of the 'enum' values would be prettier in some other order.
- A function pointer for pointing to a function whose type is useful
across all 'XXX_t'. While such a "handler" might not immediately seem
as useful as comparing against your 'enum' values, it can be useful as a
way to "query" the object; perhaps for information including... Its
type. Or the operations that are supported on that type/object. Or any
extensions available for that type/object.
- A little 'struct' type that you will be happy with for eternity. I
don't care much for this one, since I don't know what the future might
teach, and because it seems to stray from "keeping it simple," and
because it could cost more memory than some of the other options.
- A pointer to a little 'const struct' type that you will be happy with
for eternity. The pointed-to structure can be common across all 'foo_t'
and encompass information about that type.
The following example is also available (with some nice syntax
colouring) at:
http://codepad.org/sMZemHGx
/**** public.h */
/*** Object types */
typedef struct s_animal_ s_animal;
typedef struct s_cat_ s_cat;
typedef struct s_dog_ s_dog;
/*** Function types */
typedef void f_any(void);
typedef f_any * f_handler(s_animal *, f_any op);
typedef void f_pet(s_animal *);
/*** Struct/union definitions */
struct s_animal_ {
s_animal * self;
f_handler * handler;
};
struct s_cat_ {
s_animal animal;
int lives;
const char * pet_noise;
};
struct s_dog_ {
s_animal animal;
const char * pet_noise;
};
/*** Function declarations */
extern f_pet pet;
extern s_cat * make_cat(void);
extern s_dog * make_dog(void);
/**** private.c */
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
/*** Function definitions */
void pet(s_animal * animal) {
f_any * pet_func = animal->handler(animal, (f_any *)pet);
f_pet * pet_func2;
if (pet_func == (f_any *)0) {
puts("Huh? (Type \"help\" for help.)");
return;
};
pet_func2 = (f_pet *)pet_func;
pet_func2(animal);
return;
}
static f_pet pet_cat;
static void pet_cat(s_animal * animal) {
s_cat * cat = (void *)animal;
puts(cat->pet_noise);
return;
}
static f_handler handle_cat;
static f_any * handle_cat(s_animal * animal, f_any * op) {
if (op == (f_any *)pet)
return (f_any *)pet_cat;
return (f_any *)0;
}
s_cat * make_cat(void) {
s_cat * cat = malloc(sizeof *cat);
if (!cat) return NULL;
cat->animal.self = &cat->animal;
cat->animal.handler = handle_cat;
cat->lives = 9;
cat->pet_noise = "Purr, purr...";
return cat;
}
static f_pet pet_dog;
static void pet_dog(s_animal * animal) {
s_dog * dog = (void *)animal;
puts(dog->pet_noise);
return;
}
static f_handler handle_dog;
static f_any * handle_dog(s_animal * animal, f_any * op) {
if (op == (f_any *)pet)
return (f_any *)pet_dog;
return (f_any *)0;
}
s_dog * make_dog(void) {
s_dog * dog = malloc(sizeof *dog);
if (!dog) return NULL;
dog->animal.self = &dog->animal;
dog->animal.handler = handle_dog;
dog->pet_noise = "Pant, pant...";
return dog;
}
/**** test.c */
#include "public.h"
int main(void) {
s_cat * kitty = make_cat();
s_dog * doggy = make_dog();
if (kitty) {
pet(&kitty->animal);
free(kitty);
}
if (doggy) {
pet(&doggy->animal);
free(doggy);
}
return 0;
}