Using Predefined Compiler Macros in C and C++

| 4 minutes

C and C++ compilers provide a set of predefined macros which can be used to detect a wide variety of build settings and target properties at compile time. Combining them with the preprocessor allows for platform-specific code to be guarded at compile time.

Listing predefined macros

To list predefined compiler macros, we can use the following commands:

1
2
clang -dM -E -x c /dev/null
clang++ -dM -E -x c++ /dev/null

Options:

  • -dM dumps a list of macros.
  • -E prints the result to stdout instead of a file.
  • -x c and -x c++ select the source langiage when no file extension is available. If instead of /dev/null we used dummy.{c,cpp}, then these options wouldn’t be required.

clang (resp. clang++) can be replaced by gcc (resp. g++).

Detecting the target operating system

We can used predefined compiler macros to detect the target operating system at compile time when working with C and C++ code. This can allow us to conditionally enable or disable parts of the code depending on the target, which is useful when writing cross-platform code.

The following snippet detects common target operating systems:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#if defined(__linux__)
/* Linux ...........................................................*/
#elif defined(__APPLE__) || defined(__MACH__)
/* Mac OSX and Darwin (iOS) ........................................*/
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
/* iOS in XCode simulator */
#elif TARGET_OS_IPHONE == 1
/* iOS on iPhone, iPad, etc. */
#elif TARGET_OS_MAC == 1
/* Mac OSX */
#endif
#elif defined(__CYGWIN__) && !defined(_WIN32)
/* POSIX Cygwin under Microsoft Windows ............................*/
#elif defined(_WIN32)
/* 32-bit Microsoft Windows ........................................*/
#elif defined(_WIN64)
/* 64-bit Microsoft Windows ........................................*/
#endif

Detecting the target processor architecture

In addition to operating system detection, predefined compiler macros can be used to determine the target architecture for which our code is compiled. In particular, we can detect x86 and AMD64 platforms using the following predefined macros:

1
2
3
4
5
#if defined(__x86_64__) || defined(_M_X64)
/* AMD64 / x86-64 ..................................................*/
#elif defined(__i386) || defined(_M_IX86)
/* x86 .............................................................*/
#endif

Note: The _M_X64 and _M_IX86 variants are defined by the Microsoft Visual Studio compiler.

Detecting the compiler name and version

We can also leverage predefined compiler macros to detect the compiler name and version of any major C and C++ compiler at compile time.

Compiler name

The following snippet checks for the three major C and C++ compilers currently used today.

1
2
3
4
5
6
7
#if defined(__clang__)
/* Clang/LLVM ......................................................*/
#elif defined(__GNUC__) || defined(__GNUG__)
/* GNU GCC/G++ .....................................................*/
#elif defined(_MSC_VER)
/* Microsoft Visual Studio .........................................*/
#endif

Warning: Even though __GNUC__ and __GNUG__ were first intended to identify the GNU compiler suite, Clang and some other compilers also define them to indicate their compatibility with GNU behavior. To explicitly check for gcc/g++, use

1
2
3
#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__)
/* GNU GCC/G++ .....................................................*/
#endif

Compiler version

Compiler versions are encoded in a variety of ways that are not always compatible with one another.

  • Clang/LLVM
    • __clang_major__, __clang_minor__ and __clang_patchlevel__ expand to the major version, minor version and patch level of the compiler. For example, for clang 10.0.1 those macros would expand to 10, 0 and 1 respectively.
    • __clang_version__ expands to a version string whose format is left unspecified and that can vary between different distributions.
    • __GNUC__, __GNUG__, __GNUC_MINOR__ and __GNUC_PATCHLEVEL__ indicate with which version of the GNU compiler this particular version of clang is compatible.
    • __VERSION__ expands to a long version string whose format is left unspecified and that can vary between different distributions. Contrary to __clang_version__, this string also indicates GNU compatibility.
  • GNU GCC/G++
    • __GNUC__ and __GNUG__ expand to the major version number, while __GNUC_MINOR__ and __GNUC_PATCHLEVEL__ contain the minor version number and patch level of the compiler respectively. For example, for gcc 10.2.0 those macros would expand to 10, 2 and 0 respectively. The __GNUC_PATCHLEVEL__ macro was introduced in version 3.0.
    • __VERSION__ expands to a version string whose format is left unspecified and that can vary between distributions.
  • Microsoft Visual Studio
    • _MSC_VER expands to the major and minor version numbers as a single integer (e.g. 1500 for version 15.00).
    • _MSC_FULL_VER expands to the major version, minor version and build numbers as a single integer (e.g. 150020706 for version 15.00.20706). This macro was introduced in Visual Studio 2008.
    • _MSC_BUILD expands to the revision number that appears after the major version, minor version and build numbers in the version identifier (e.g. 1 for version 15.00.20706.01). This macro was introduced in Visual Studio 2008.

See also

The following links point to resources that were once available online but are only accessible to the Internet Archive today. They list additional predefined macros for less common compilers and target platforms.