5

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?

josch
  • 6,716
  • 3
  • 41
  • 49
  • 4
    There's no need to declare `struct _foo_s;` after `typedef struct _foo_s foo_t;`. That typedef alone already declares *both* `struct _foo_s` and `foo_t`. The following `struct _foo_s;` adds nothing to it. It is redundant. Maybe that is exactly what iwyu was trying to tell you. – AnT stands with Russia Sep 19 '14 at 17:40
  • 1
    The code that you are showing doesn't give rise to a double `typedef`. You should have no problem if you have working include guards protecting the contents of your `.h` file. – Jens Gustedt Sep 19 '14 at 17:44
  • 1
    OT: POSIX does not allow types ending with `_t`. – alk Sep 19 '14 at 18:32
  • 1
    @alk: That's slightly overstating the case; POSIX reserves type names ending `_t`, which means that if you create your own type matching that, you may run into problems at any time and it is your own fault. That is not as stringent as 'forbids', though it is a lot more verbose. Note that the C standard reserves most names starting with an underscore, so the name `_foo_s` is treading on similar thin ice. (ISO/IEC 9899:§7.1.3 Reserved Identifiers: _All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces._ – Jonathan Leffler Sep 19 '14 at 18:58
  • Related: http://stackoverflow.com/q/621356/694576 – alk Sep 19 '14 at 19:00
  • Hmhm, "*reserved*" means "*do not use*", "*not allowed*"/"*forbidden*" means "*do not use*", so what? However I agree that the former is the less harsh wording for the latter ... - which is nice. @JonathanLeffler – alk Sep 19 '14 at 19:03
  • @alk: I'd interpret _reserved_ as 'use at your own risk' and _forbidden_ as 'do not use'. We can agree to differ on the emphasis; the net result is the same — the best advice is to avoid creating typedef names ending `_t` because it may cause you trouble later, even if it works OK now. – Jonathan Leffler Sep 19 '14 at 19:06
  • @JonathanLeffler: Hehe, I tend to treat both hints as "*use at your own risk*" ... ;-) But I fully agree with you in advising to "*avoid ... trouble*"! – alk Sep 19 '14 at 19:10

1 Answers1

4

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 */
}
Arun
  • 19,750
  • 10
  • 51
  • 60
  • 1
    1+ but I'd get rid of the `typedef`. It's useless. Replace `typedef struct foo_t foo_t;` with `struct foo;` and just stick to using `struct foo`. – alk Sep 19 '14 at 18:24
  • 1
    @alk: Thanks! OP was already quite close, I wanted to do minimum change... I guess the OP want the clients to use just `foo_t`, not `struct foo_t`; so I left it as is. I understand the POSIX restriction too, but that is a different topic. – Arun Sep 19 '14 at 18:30
  • You are correct, I moved up my comment on `_t` to the OP. – alk Sep 19 '14 at 18:32