Tob Ernack
Tob Ernack

Reputation: 241

Using external header file with definitions different from the ones used internally

Suppose you are writing a library that uses internally certain data structures, and wants to export to the user only a subset of them (or hide the exact type using something like void *). The definitions for all the structs and functions used in the library are in a header library.h, which will be used when building the library.

Is it considered good practice to also produce another copy of library.h that would not be used during the build process but only by users linking to the library?

For example suppose the library internally uses the following library.h:

#ifndef LIBRARY_H
#define LIBRARY_H

struct myStruct {
    int some_x;
    void (*some_callback)(void);
};

typedef struct myStruct *myStruct_t;

#endif

While we would like to hide the definition of myStruct to the user, so we export a header library.h that is:

#ifndef LIBRARY_H
#define LIBRARY_H

typedef void *myStruct_t;

#endif

Upvotes: 1

Views: 357

Answers (2)

Notice that the C standard doesn't guarantee that a pointer to void has a representation that is compatible with a pointer to a struct! Thus:

typedef struct myStruct *myStruct_t;
typedef void *myStruct_t;

these two are not compatible and cannot be used in a strictly conforming program.


Another thing is that you usually shouldn't hide pointers, unless needed. Consider for example the FILE in the standard library. Its contents are not defined anywhere, but all the functions specifically return a pointer to it and accept a pointer to it.

You can even use a simple struct declaration, instead of definition:

struct myStruct;

Then external users can define a variable as a pointer to it

struct myStruct *handle;

Or if you wish to hide the fact that it indeed is a struct, use a typedef:

typedef struct myStruct myStruct;

Then the users of the external interface can define their variables simply as

myStruct *handle;

Upvotes: 2

user2371524
user2371524

Reputation:

Is it considered good practice to also produce another copy of library.h that would not be used during the build process but only by users linking to the library?

No. While the details of a best practice for what you want to do are probably a matter of taste, delivering headers not used during building is objectively not a good practice: You risk to introduce typing errors that are never catched when you build your project.

So, without going into details on how you should organize that, what you should definitely do is have each "private" header #include the respective "public" header and not repeat public declarations in the private header. For your example, this would look e.g. like:

library.h:

#ifndef LIBRARY_H
#define LIBRARY_H

typedef struct myStruct *myStruct_t;
// there's absolutely no need to use void * here. An incomplete struct
// type is perfectly fine as long as only pointers to it are used.

#endif

library_internal.h:

#ifndef LIBRARY_INTERNAL_H
#define LIBRARY_INTERNAL_H
#include "library.h"

struct myStruct {
    int some_x;
    void (*some_callback)(void);
};

#endif

Additional "best practice" notes:

  • Don't hide pointers behind typedefs. Most C programmers are well aware that a pointer is part of the declarator and expect to explicitly see a pointer when there is one. Dereferencing something that doesn't look like a pointer will just cause confusion for others reading the code. You also might confuse consumers of your library into expecting a myStruct_t to exhibit call-by-value semantics.

  • Don't define your own types with the _t suffix. At least in POSIX, this is reserved for the implementation (of the compiler/runtime). There's nothing wrong with defining a type of the same name as a struct tag.

Example with these additional suggestions:

library.h:

#ifndef LIBRARY_H
#define LIBRARY_H

typedef struct myStruct myStruct;

#endif

library_internal.h:

#ifndef LIBRARY_INTERNAL_H
#define LIBRARY_INTERNAL_H
#include "library.h"

struct myStruct {
    int some_x;
    void (*some_callback)(void);
};

#endif

Upvotes: 2

Related Questions