user149408
user149408

Reputation: 5901

Good practice for types in C which are not needed in certain configurations

I am writing an abstraction layer for thread-related functions in a C project. Not all platforms for which the code can be built support multithreading; alternatives are available for these platforms.

The header file defines a preprocessor macro if multithreading is supported, so preprocessor conditionals can be used to enable and disable blocks of code which are only required with multithreading (or only without).

Functions are thin wrappers around their native implementations, which can differ from platform to platform. Types are mapped to the corresponding native types with

#define mythread pthread_t

and similar.

In order to avoid having to clutter code with conditionals every time a lock needs to be acquired or released, most functions can be called even in single-threaded builds, which makes them no-ops. Conditionals are therefore needed only when spawning or joining threads, or for the substitutes thereof. (Attempting to call the thread creation function on a single-threaded build will generate a compiler error.)

This also means the abstracted thread types need to map to something even when threads are not supported, as there may be local variables of these types (again, to avoid cluttering the code with conditionals). So far I am mapping them to void on single-threaded builds like this:

#ifdef HAVE_POSIX_THREADS
#define mythread pthread_t
#else
#ifdef HAVE_FUNKY_THREADS
#define mythread FThread
#else /* no supported native thread API available, single-threaded build */
#define mythread void
#endif

This, however, comes at the expense of not being able to use declarations like:

mythread new_thread;

(which would declare a void variable, invalid in C). That could be worked around by working only with pointers to these types—in fact, every function that initializes any of these types allocates the memory and returns a pointer to it.

The question: I was wondering if there is a more elegant way for situations like these.

Upvotes: 2

Views: 69

Answers (1)

BlueFlo0d
BlueFlo0d

Reputation: 180

It is okay to just do

#else
#define mythread int
#endif

And you can probably make all your thin wrapper functions static inline xxx xxx(xxx), though this is not really necessary because almost every modern compilers just treat inline as a hint. After doing these, almost every modern compiler will inline your wrapper functions and figure out that new_thread in your mythread new_thread is actually unused then optimize it out.

Note: (Just personal comment, probably off-topic) Do not use macro conditionals unless necessary. Compiler optimizations like dead code and unused variable elimination is your good friend. Though not in your case, in the GNU coding guideline it is encouraged to use

if(some_compiler_flag){ //some_compiler_flag is an 0/1 flag
xxx;
}

instead of

#ifdef some_compiler_flag
xxx
#endif

Modern compilers will compile to the same result, and the previous one is more readable and more friendly to static analysis tools and auto-completion, which make your life easier.

In your case, if it is possible to restructure your code I'll prefer to use all those thread types as opaque pointers. For example,

//In public headers
union ptr_my_thread_;
typedef ptr_my_thread union ptr_my_thread_;
int my_thread_init(ptr_my_thread);
...

And then deal with platform-specific code in your implementation files

//In config.h, which takes care of platform dependent flags
#define HAVE_POSIX_THREADS 1
#define HAVE_FUNKY_THREADS 0
...

//In your implementation file
#include <config.h>
union ptr_my_thread_{
    pthread_t *posix;
    FThread *funky;
}
int my_thread_init(ptr_my_thread new_thread){
    if(HAVE_POSIX_THREADS){
        xxx;
    }
}

This will enforce modularity and separation of platform dependent code.

Upvotes: 1

Related Questions