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?
A re-entrant function is one that can be interrupted (typically during thread context-switching), and re-entered by another thread without any ill-effect. Functions that rely on a local variable are considered re-entrant due to the fact that their variables are safely encapsulated between threads.
The print process is usually a sequence of copying data to buffer and flushing the buffer afterwards. This buffer should be protected by locks in the same way malloc does. Therefore, printf is also non-reentrant.
An operation is “thread-safe” if it can be performed from multiple threads safely, even if the calls happen simultaneously on multiple threads. An operation is re-entrant if it can be performed while the operation is already in progress (perhaps in another context).
In computing, a computer program or subroutine is called reentrant if multiple invocations can safely run concurrently on multiple processors, or on a single processor system, where a reentrant procedure can be interrupted in the middle of its execution and then safely be called again ("re-entered") before its previous ...
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With