Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conflicting anonymous forward declaration in header

EDIT: changed foo_t to foo as a typename because POSIX reserves types ending in _t EDIT: changed _foo_s to foo_s because C claims names starting with an underscore

I'm puzzled about what the best way is to have the following at the same time:

  1. the library implementation sees the struct members but users of the library do not
  2. the compiler checks whether the function definition in the header matches the implementation
  3. use C99

My first stab at this was to do the following:

foo.h (inclusion guards omitted for brevity):

typedef struct foo_s foo;

struct foo_s;

foo* foo_create(void);
void foo_do_something(foo *foo);

foo.c:

#include "foo.h"

struct foo_s {
    /* some_hidden_members */
};

foo* foo_create() {
    /* allocate memory etc */
}

void foo_do_something(foo *foo) {
    /* do something with foo */
}

This seems to work with gcc. Everybody who includes foo.h only sees the anonymous forward declaration and the real layout of struct foo_s is only known in foo.c.

I started to smell something odd with the above when I tried using include-what-you-use which uses clang. When I used it to check foo.c it informed me that foo.h should not contain the forward declaration of struct foo_s. I thought it was a bug in iwyu because obviously this would not be a problem for anybody else who includes foo.h.

At this point let me come to my second requirement from the beginning. foo.c includes foo.h so that the compiler can make sure that every function declared in foo.h matches the implementation in foo.c. I think I need this because I ran into segmentation faults too often because the function signature of my implementation did not match the one in the header that other code used.

Later I tried compiling the code with clang (I compile with -Wall -Wextra -Werror) and was informed that:

error: redefinition of typedef 'foo' is a C11 feature

I don't want my code to depend on a C11 feature and I do want to be sure that the functions in the public header match the implementation. How do I solve that?

I see a way which is to split foo.h into foo.h and foo_private.h:

foo.h (inclusion guards omitted for brevity):

struct foo_s;

#include "foo_private.h"

foo_private.h (inclusion guards omitted for brevity):

typedef struct foo_s foo;

foo* foo_create(void);
void foo_do_something(foo *foo);

And then I would include foo_private.h in foo.c and other code would include foo.h. This would mean that foo.c does not see the forward declaration of foo_s anymore and thus clang and iwyu should be happy. It also means that the implementation of my functions is checked to match the header.

But while this works it makes me wonder whether this is the best solution because:

  • it seems a waste to have one header file with only two lines
  • I do not know of other projects that do it like that (and looking in my /usr/include I don't see any either)

So what would a the solution be that fulfills the three criterea listed at the top? Or is the solution I found the one to go?

like image 415
josch Avatar asked Sep 30 '22 20:09

josch


1 Answers

Kudos on the noble intention of data hiding!

How about the following?

foo.h (inclusion guards omitted for brevity):

typedef struct foo_t foo_t;    // note change 0

// note change 1

foo_t* foo_create(void);
void foo_do_something(foo_t *foo);

foo.c:

#include "foo.h"

struct foo_t {                // note change 2
    /* some_hidden_members */
};

foo_t* foo_create() {
    /* allocate memory etc */
}

void foo_do_something(foo_t *foo) {
    /* do something with foo */
}
like image 82
Arun Avatar answered Oct 30 '22 12:10

Arun