4

So I am curious how to hide implementation of the struct in .c file if we need to refer to its instance in header file. For example I have the following struct in header file:

struct List
{
    NodePtr front;    
};

I want to declare NodePtr in .c file to hide it's implementation. In .c:

struct Node
{
    void *value;
    struct Node *next;
};

typedef struct Node *NodePtr;

But of course than .h file does not know what NodePtr is....

How would I do that in the right way?

YohanRoth
  • 3,153
  • 4
  • 31
  • 58
  • Use anonymous structure pointers `struct Node *`. – chux - Reinstate Monica Sep 28 '14 at 23:12
  • In this setup, it should suffice to just remove the `struct`-definition (but not the `typedef`!) from the header file. – misberner Sep 28 '14 at 23:14
  • The fundamental question is: will the clients of the header ever need to know the internals of the structure, or allocate a complete structure rather than a pointer to the structure. If the answer to either question is 'yes', then you'll need to define the body of the structure type in the header. If the answer is 'no' — clients only use pointers to the type and never directly access any members of the structure type — then you don't need the body of the structure type in the header. – Jonathan Leffler Sep 29 '14 at 00:00

2 Answers2

5

Something like this should work fine. Note that the definition of struct Node never leaves List.c.

list.h

#pragma once
#include <stdbool.h>

struct List {
    struct Node *front;
};

void list_init(struct List **list);
void list_free(struct List *list);
void list_insert(struct List *list, void *value);
bool list_contains(struct List *list, void *value);

list.c

#include <stdbool.h>
#include <stdlib.h>
#include "list.h"

struct Node {
    void *value;
    struct Node *next;
};

void list_init(struct List **list) {
    *list = malloc(sizeof(**list));
}

void list_free(struct List *list) {
    struct Node *node = list->front;
    while (node != NULL) {
        struct Node *next = node->next;
        free(node);
        node = next;
    }
    free(list);
}

void list_insert(struct List *list, void *value) {
    struct Node *node = malloc(sizeof(*node));
    node->value = value;
    node->next = list->front;
    list->front = node;
}

bool list_contains(struct List *list, void *value) {
    struct Node *node;
    for (node = list->front; node != NULL; node = node->next)
        if (node->value == value)
            return true;
    return false;
}

main.c

#include <stdio.h>
#include "list.h"

int main() {
    struct List *l;
    list_init(&l);

    int *value_1 = malloc(sizeof(int));
    int *value_2 = malloc(sizeof(int));
    int *value_3 = malloc(sizeof(int));

    list_insert(l, value_1);
    list_insert(l, value_2);
    list_insert(l, value_3);

    printf("Does the list contain value_1: %d\n", list_contains(l, value_1));
    printf("Does the list contain null:    %d\n", list_contains(l, NULL));

    list_free(l);
    return 0;
}

It's very possible that I have some errors in this code. If you see any, feel free to fix them.

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • 1
    As a matter of idle fact, the header doesn't need the definition of the content of `struct List`; you could simply use `struct List;` in the header (before any of the function prototypes). You only need the definition of the structure if the client code would contain an instance of the structure, but you've set things up so the client code only needs `struct List *`. But +1 for the first step in the simplification. – Jonathan Leffler Sep 28 '14 at 23:47
  • You need to declare `struct Node;` before `struct List { ...` or the `struct Node` inside the latter will not be the same type as the file-scope `struct Node` but rather a separate struct type whose scope is local to `struct List`. – R.. GitHub STOP HELPING ICE Sep 28 '14 at 23:47
  • Also, please don't use `#pragma once` in examples. It's not valid C. – R.. GitHub STOP HELPING ICE Sep 28 '14 at 23:48
  • @R..: C is not C++, is it? – Jonathan Leffler Sep 28 '14 at 23:48
  • @JonathanLeffler: Am I mistaken about that? Perhaps. Certainly the issue exists for `struct Foo` declared inside a function prototype, but perhaps it's not an issue inside another struct. It's been a while since I read that part of the standard. – R.. GitHub STOP HELPING ICE Sep 28 '14 at 23:49
  • @R..: Inside a function prototype is a different issue — that's why my comment says `struct List;` before any prototypes. But at global scope, all references to `struct Node` would be references to the same single `struct Node`. Inside a function, you can get more fancy (new scope, new names — worry about self-referencing pairs of structure types, etc). But the `struct Node` inside the definition of `struct List` is the same as the `struct List` defined at file scope. – Jonathan Leffler Sep 28 '14 at 23:51
  • 1
    @R..: See also [Which part of the C standard allows this to compile?](http://stackoverflow.com/questions/12200096/) and [Does the C standard consider that there are one or two `struct uperms_entry` types in this header?](http://stackoverflow.com/questions/11697705/) — they're two parts of my learning experience… – Jonathan Leffler Sep 28 '14 at 23:57
  • Thanks. Great question you linked, +1'd it. – R.. GitHub STOP HELPING ICE Sep 29 '14 at 00:12
2

You have all the right to say:

typedef struct Node *NodePtr;
struct List
{
    NodePtr front;    
};

this typedef struct Node *NodePtr; is a forward declaration. You can use it for as long as you use pointers to types. Pointers need no knowledge about the type's (classes) structure. And the compiler is happy.