Reentrant library design in C
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?
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:
- you can change the contents of the struct without recompiling all client code
- the client code cannot access the members, ie. quuxState->state would give a compiler error
- the QuuxState structure will still be fully visible to the debugger, so you can see the values trivially and set watchpoints, etc.
- no casting necessary
- 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.
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.
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 **);
精彩评论