1

When developing a shared library in C, it is common to separate the "public" headers from the "private" headers. The public headers contain all the functions and types that are meant to be used by the end-user of the library (i.e., another developer), while the private headers contain internal implementation details.

What is the best way to deal with situations where the exact makeup of a struct is meant to be a private implementation detail, but your public-facing functions deal with pointers of that struct type?

I've arrived at a somewhat awkward approach with a preprocessor define. Is there a more standard pattern, or perhaps an approach that sidesteps the problem altogether?

linked_list.c

#define __LINKED_LIST_PRIVATE__
#include "linked_list.h"

linked_list_node *llist_node_create(void *userdata)
{
    linked_list_node *node = malloc(sizeof(linked_list_node));
    node->next = NULL;
    node->userdata = userdata;
}

void llist_node_destroy(linked_list_node *node)
{
    free(node);
}

void llist_node_insert_after(linked_list_node *parent, linked_list_node *new)
{
    parent->next = new;
}

void *llist_node_get_userdata(linked_list_node *node)
{
    return node->userdata;
}

linked_list_node *llist_node_get_next(linked_list_node *node)
{
    return node->next;
}

inc/linked_list_private.h

typedef struct linked_list_node_s {
    struct linked_list_s *next;
    void *userdata;
} linked_list_node;

inc/linked_list.h

#ifndef __LINKED_LIST_PRIVATE__
// so that public users of the library can still have
// the "linked_list_node" as a type, but don't know
// the internal makeup of the structure.
typedef void linked_list_node;
#else
#include "linked_list_private.h"
#endif

linked_list_node *llist_node_create(void *userdata);
void llist_node_destroy(linked_list_node *node);
void llist_node_insert_after(linked_list_node *parent, linked_list_node *new);
void *llist_node_get_userdata(linked_list_node *node);
linked_list_node *llist_node_get_next(linked_list_node *node);
2

The easiest way is to use the PIMPL idiom, also colloquially called “compilation firewall”.

This technique does not need conditional compilation to define the structure differently: in your public header, you just define opaque pointers to a struct which you do not define. In this case C will handle the struct as an incomplete type, meaning that you can only use pointers to it, but never dereference it nor get its size.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.