Suppose I have a struct declaration in a header file like:
event.h
struct event_t;
and in the corresponding C file I would like to sort-of alias it with the Linux-specific struct inotify_event
. The problem is that struct inotify_event
contains flexible array member:
struct inotify_event {
int wd;
uint32_t mask;
uint32_t cookie;
uint32_t len;
char name[];
};
As per 6.7.2.1(p3)
(emphasize mine):
A structure or union shall not contain a member with incomplete or function type (hence, a structure shall not contain an instance of itself, but may contain a pointer to an instance of itself), except that the last member of a structure with more than one named member may have incomplete array type; such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array.
it is not possible to define the struct event_t
as
struct event_t{
struct inotify_event base; //Non-conforming
};
So I could convert struct event_t *
to struct inotify_event *
. Since the 6.7.2.1(p3)
concerns only about structs the solution I see is to redeclare the tag name as
union event_t
and then define it later as a single element union.
union event_t{
struct inotify_event event; //Conforming?
};
The only requirement the Standard imposes on union that I found is that the set of members of a union must be non-empty 6.2.5(p20)
(emphasize mine):
A union type describes an overlapping nonempty set of member objects, each of which has an optionally specified name and possibly distinct type.
QUESTION: Is it a conforming/common way to hide an implementation details of some specific data structure through union
?
By far the simplest technique is to put this into your
event.h
header:This declares that there is a structure type
struct inotify_event
and declares an alias for itevent_t
. But it does not define the content ofstruct inotify_event
at all.Only the implementation code in
event.c
includes the definition ofstruct inotify_event
from the system header; everything else does not include that header and cannot access the elements of anevent_t
except through the accessor API you define.You can enforce this separation of duties by code review — or by checking with
grep
, or other similar techniques — to ensure that no code except the implementation of your event type uses the system header forinotify_event
. And, if you port to a system other than Linux without support for inotify, then you simply provide an alternative opaque structure type in place ofstruct inotify_event
in yourevent.h
header.This avoids all questions about whether there are flexible array members within structures, etc; it is all a non-issue.
Note the Q&A about What does a type followed by
_t
(underscore t) represent? . Be cautious about creating your own types with the_t
suffix ¸— consider using a prefix on such type names that gives you a chance that your names will be distinct from those provided by the system.This is how I would do it:
event.h
event.c
However, if you want to store additional data with the event then:
event.h
event.c
If you have multiple implementations that you need to hide in
event_t
then:Single element union makes no sense. The purpose of union is to serve as a kind of polymorphic struct. struct members are accessed by offset, this is why is impossible to put an incomplete struct or array in the middle of a struct.
For example
In this example is impossible for the compiler to determine the address of
c
becauseb
size can vary at runtime. But if you put incomplete array at the end all struct members address can be determined by the address of the start of the struct. Keep in mind that pointers are just address, so you can have any pointers to any structs and all the offsets can be determined, but you will need to deal with extra alloc/free stuff.When you create an union you telling to compiler Hey! I have this members, reserve enough space for me so that I can treat this variable as foo or bar. In another words, the compiler will take the largest union member and this will be the size of the union. A common use for union is for representing multiple kinds of values.
This way you can treat
value_type
asint
,float
, or achar
pointer. You're code need to know the how to treat each member but the compiler will make sure that when you domalloc(sizeof value_type)
you have enough space for the tree types.Now your problem. You want to hide implementation details. Usually this is done by declaring a type or struct incompletely in a header, and completely only on your object files. Because of this when the user include your header all the information that the compiler has is
struct my_struct;
. It can't tell the size ofmy_struct
so is impossible for you to allocate it asmalloc(sizeof struct my_struct)
. Also since the user hasn't the member definitions it can't mess up with the struct internals.Working like this you will need to provide user with functions for allocating and freeing
my_struct
, for examplestruct my_struct *foo = my_struct_new()
andmy_struct_destroy(foo)
.You're already doing this. To deal with the
struct inotify
problem I would do one of these.(1) Surround OS specific with
#ifdef
for that OS, so that theevent_t
has only the right members defined depending on the operating system. You will need#ifdef
on your functions. This has the advantage to keep useless code out of final binary, so smaller footprint.(2) Have pointers to OS specific structs and let runtime decide what to do. This easier to maintain.