How do I type the structure defined by the implementation in the general header?

I have a C project that is designed for portability to various (PC and embedded) platforms.

Application code will use various calls that will have implementations on the platform, but share a common (common) API to ensure portability. I am trying to find the most suitable way to declare prototypes of functions and structures.

Here is what I have come up with so far:

main.c:

#include "generic.h" int main (int argc, char *argv[]) { int ret; gen_t *data; ret = foo(data); ... } 

generic.h: (including agnostic platform)

 typedef struct impl_t gen_t; int foo (gen_t *data); 

impl.h: (platform specific ad)

 #include "generic.h" typedef struct impl_t { /* ... */ } gen_t; 

impl.c: (implementation on the platform)

 int foo (gen_t *data) { ... } 

Build:

 gcc -c -fPIC -o platform.o impl.c gcc -o app main.c platform.o 

Now it works ... in that it compiles OK. However, I usually do not bind my structures, as they are never accessible outside of typedef 'd aliases. This is a little nit kick, but I wonder if there is a way to achieve the same effect with anonymous structures?

I also ask for posterity since I was looking for some time, and the closest answer I found was as follows: ( Link )

In my case, this will not be the right approach, since the application should not specifically include implementation headers directly - the thing is to separate the program from the platform.

I see a couple of other, but not ideal ways to solve this problem, for example:

generic.h:

 #ifdef PLATFORM_X #include "platform_x/impl.h" #endif /* or */ int foo (struct impl_t *data); 

None of them seem particularly attractive, and definitely not my style. Although I do not want to swim upstream, I also do not want a contradictory style, when there may be a more convenient way to realize exactly what I had in mind. So I think the typedef solution is on the right track, and it's just baggage with the struct tag that I stay with.

Thoughts?

+5
source share
1 answer

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 .

+4
source

Source: https://habr.com/ru/post/1206463/


All Articles