user18917702
user18917702

Reputation:

Determine the operating system variable in a C preprocessor directive

I am posting yet another programming task that I can't solve. Suppose we write OS-specific preprocessor directives like:

#if OS == Linux
  /* Linux-specific instructions */
  ...
#else
  #if OS == Darwin
    /* Mac OSX-specific instructions */
    ...
  #else
    /* Program code for other OS */
    ...
  #endif
#endif

We are not allowed to use #define OS Linux; instead the variable OS should be set with the compiler (or alternatively in a makefile). There should also be a shell-command which can automatically define OS. How can we set OS-Variable using the compiler or the shell?

Kind regards

Upvotes: 1

Views: 346

Answers (1)

The C preprocessor can only compare integer values, not strings or any other type. When you write #if OS == Linux, the preprocessor first expands the macros OS and Linux (and any macros in their expansions, etc.). Once it's finished expanding, it treats the whole line (after #if itself) as a preprocessor expression and evaluates it. Like with the C language proper, the directive is true if the value is nonzero, and boolean operators like == evaluate to 1 if true and 0 if false.

So for #if OS == Linux to work, you have to choose a numerical value for Linux (and a different value for all the other operating systems). Then you need to assign OS to the same value. For example, if suppose you have a header operating_systems.h containing

#define Linux 1
#define Darwin 2
#define Windows 3

then you can use #define OS 1 or #define OS (7 - 6) or #define OS Linux or any equivalent method to make #if OS == Linux true.

Note that if Linux and Darwin are undefined and you define OS to either Linux or Darwin, then both OS == Linux and OS == Darwin will be true, because undefined macro names in preprocessor expressions have the value 0. This kind of “enum-like” conditional is error-prone since undefined values (or values from the wrong “enum” series) will not be caught, so it isn't a good idea.

This is quite an unusual way to select between operating systems. Usually, system selection is based on whether certain macros are defined or not. For example, all compilers for Linux should define __linux__, all compilers for macOS should define __APPLE__, etc. To find out what macros are predefined, consult the documentation of your compiler or operating system (including applicable standards, e.g. POSIX), or ask your compiler (e.g. cpp -dM /dev/null).

So typical OS-specific code looks like this:

#if defined(__APPLE__)
// macOS or iOS code
#elif defined(__unix__)
// code for Unix-like systems
#  if defined(__linux__)
  // additional code for Linux
#  endif
#elif defined(_WIN32) || defined(_WIN64)
// code for Windows
#else
#error "Unknown operating system"
#endif

If you need to select additional information during the build which can't be determined by the compiler alone, most compilers have a way to predefine additional macros. On Unix-like compilers (including GCC, Clang and many others), you do this by passing -DMACRO_NAME or -DMACRO_NAME=expansion on the command line. For example, if you compile with

gcc -DPROTOCOL_VERSION=3

then you can write code like this:

#if PROTOCOL_VERSION == 1
#error "Protocol version 1 is no longer supported."
#elif PROTOCOL_VERSION == 2
// code for version 2
#elif PROTOCOL_VERSION == 3
// code for version 3
#else
#error "An unsupported protocol version was requrested"
#endif

(This example assumes your code is only capable of supporting a single protocol version.)

Upvotes: 2

Related Questions