Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 245
Vinko Vrsalovic Avatar asked Jul 13 '10 13:07

Vinko Vrsalovic


People also ask

What is reentrant in C?

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.

Is printf re-entrant?

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.

What is difference between reentrant and thread-safe functions?

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).

What is reentrant in operating system?

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 ...


2 Answers

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.

like image 99
JosephH Avatar answered Sep 23 '22 21:09

JosephH


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.

like image 38
Seva Alekseyev Avatar answered Sep 24 '22 21:09

Seva Alekseyev