views:

132

answers:

3

Let's say I'm building a library to spork quuxes in C.

Quuxes need two state variables to be sporked successfully:

static int quux_state;
static char* quux_address;

/* function to spork quuxes found in a file, 
   reads a line from the file each time it's called. */
void spork_quux(FILE*);

If I store that data as global variables, only a single client will be able to spork quuxes at a single time, else the state variables will get mangled by a second caller and disaster may ensue.

The question is what's the best way to design a reentrant library in C?

I've entertained the following cases, to no satisfactory conclusion.

In the following case the question is how to associate a client to each state?

/* library handles all state data allocation */
static int* quux_state; 
static char** quux_address;

In the following case the client is able to mess with the state, very undesirable

/* let each client store state */
typedef struct { int state; char* address; } QuuxState; 
QuuxState spork_quux(FILE*);

So, how to do this properly?

+3  A: 

Use a struct, but don't tell the client it's a struct. Pass out an opaque pointer - void*, or better yet a pointer to an empty dummy struct - and cast it back when needed.

Seva Alekseyev
Or a pointer to an incomplete type, i.e. put `struct quux;` in your header file with no definition, and then return `struct quux *`. The definition can remain in a private header file used/visible only to your library code.
R..
+18  A: 

Use a struct, but don't expose the definition to the client.

ie. in the .h header file put:

typedef struct QuuxState QuuxState;

QuuxState *spork_quux(FILE*);

and in the .c implementation file:

struct QuuxState
{
    int state;
    char* address;
};

QuuxState *spork_quuxFILE *f)
{
    QuuxState *quuxState = calloc(1, sizeof(*quuxState));
    if (!quuxState)
        return NULL;

    quuxState->state = ....;

    ........

    return quuxState;
}

The advantages of this approach is that:

  1. you can change the contents of the struct without recompiling all client code
  2. the client code cannot access the members, ie. quuxState->state would give a compiler error
  3. the QuuxState structure will still be fully visible to the debugger, so you can see the values trivially and set watchpoints, etc.
  4. no casting necessary
  5. the type you're returning is a specific type, so you will get some compiler checking that the correct thing is being passed (compared to a void* pointer)

The only disadvantage is that you have to allocate a block of memory - however assuming your library is doing anything not trivial (and if it's doing file I/O, that's certainly non-trivial) the overhead of a single malloc is negligible.

You might want to rename the above function to something like 'QuuxSpork_create', and add more functions to deal with performing the line-by-line work and destroying the state once you're done.

void QuuxSpork_readLine(QuuxState *state)
{
    ....
}

void QuuxSpork_destroy(QuuxState *state)
{
    free(state);
}

A random example of a library that works roughly like this would be the POSIX threading library, pthreads.

JosephH
Nice answer, Joseph! +1
Jonathan Leffler
Re: 'disadvantage': the code could statically allocate an array of struct QuuxState structures, and return a new one each time until it runs out. When the program finishes sporking its quux, it calls 'unspork_quux()' or something to return the value. This places a finite bound on the number of quux's that can be sporked at a time. It that's still a problem, then you can dynamically allocate after running out of the static allocation, running a (nominal rather than practical) risk of undefined behaviour when testing whether the returned pointer lies within the bounds of the static array.
Jonathan Leffler
Thanks! That's definitely a way that would work - of course the usual rules about avoid premature optimisation and keeping code simple would apply, but if you were using this pattern in a tight loop or otherwise very performance critical (or on an embedded system) a preallocated approach like you outline would definitely be prudent and likely necessary.
JosephH
+1 This is pretty much the standard pattern used in C to encapsulate objects. You'll see it being used all over the place. Nicely explained too.
JeremyP
By the way, if you find you are allocating and freeing lots of Quuxs, you can add a pointer to a Quux to the struct definition and instead of freeing them, add them to a list of unused Quuxs. When you need a new one, just take one off the top of the list, unless its empty, in whch case just malloc a fresh one.
JeremyP
A: 

The way most library functions handle this is to give the state information back to the user in whatever data type they need. In your case, a struct. (take strtok vs strtok_r). I believe that this sets a precedent that you should pass it back to the user. A void * works. you could even typedef it so that it looks pretty.

Moreover, strtok_r does this by editing the command line argument, not returning a pointer to the state. I would expect any re-entrant function i use to follow a similar format. Of course, my brain has been warped by some pretty crazy C code.

void spork_quux(FILE*, QuuxState **);
Rannick