Your current technique is correct. Trying to use an anonymous (untagged) struct wins what you are trying to do - you would need to expose the details of the struct definition everywhere, which means you no longer have an opaque data type.
Comment user3629249 says:
The order of inclusion of the header file means that there is a direct link to the structure using the generic.h file; that is, before a structure is defined, it is used. It is unlikely that this compiled.
This observation is not true for the headings indicated in the question; it is accurate for sample main() code (which I did not notice before adding this answer).
The key point is that the displayed interface functions accept or return pointers of type gen_t , which, in turn, maps to a pointer to struct impl_t . While client code does not need to allocate space for a structure or dereference a pointer to a structure to access a member of the structure, client code does not need to know the details of the structure. It is enough to have a type of structure declared as existing. You can use any of them to declare the existence of struct impl_t :
struct impl_t; typedef struct impl_t gen_t;
The latter also introduces the alias gen_t for the type struct impl_t . See Also. What part of the C standard allows this code to be compiled? and Does the C standard mean that there are one or two struct uperms record types in this header?
Original main() program in question:
int main (int argc, char *argv[]) { int ret; gen_t data; ret = foo(&data); … }
This code cannot be compiled with gen_t as an opaque (non-pointer) type. It will work fine:
typedef struct impl_t *gen_t;
It will not compile with:
typedef struct impl_t gen_t;
because the compiler needs to know how big the structure is to accommodate the right space for data , but the compiler cannot know this size by definition of what an opaque type is. (See Is this a good idea for typedef pointers? For pointing pointers to structures.)
Thus, the main() code should look more like:
#include "generic.h" int main(int argc, char **argv) { gen_t *data = bar(argc, argv); int ret = foo(data); ... }
where (for this example) bar() is defined as extern gen_t *bar(int argc, char **argv); so it returns a pointer to the opaque gen_t type.
Opinion is divided on whether it is always better to use struct tagname or use typedef for the name. The Linux kernel is one essential code that does not use the typedef mechanism; all structures are explicitly struct tagname . C ++, on the other hand, eliminates the need for an explicit typedef ; scripture:
struct impl_t;
in a C ++ program means that the name impl_t now a type name. Since opaque structure types require a tag (or you end up using void * for everything that is bad for a whole legion of reasons, but the main reason is that you lose all type safety with void * ; remember, typedef introduces an alias for base type, not a new separate type), the way I code in C mimics C ++:
typedef struct Generic Generic;
I do not use the suffix _t for my types because POSIX reserves _t for implementation to use * (see also What does the type followed by _t do? ). You may be lucky and leave. I worked on dec_t where types such as dec_t and loc_t were defined by the loc_t (which was not part of the implementation, where "implementation" means the C compiler and its supporting code, or the C library and its supporting code), and both these types have caused pain for decades, because some of the systems in which the code was ported defined these types, as well as the prerogative of the system. One of the names that I managed to get rid of; the other I did not do. "It hurts! If you should use _t (this is a convenient way to indicate that something is a type), I recommend that you also use the distinguishing prefix: pqr_typename_t for some pqr project, for example.
* See the bottom row of the second table in the POSIX Namespace .