Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MicrosoftDocs/cpp-docs/llms.txt

Use this file to discover all available pages before exploring further.

A macro is a name that the preprocessor replaces with a specified token sequence before the compiler ever sees the source file. Macros are defined with #define and removed with #undef. The preprocessor expands macros in all lines except preprocessor directive lines — those that have # as the first non-whitespace character. Understanding macro expansion rules, operator interactions, and the pitfalls of incorrect macro design is essential for writing robust, portable C and C++ code with MSVC.

Object-Like Macros

An object-like macro takes no arguments. It is the simplest form: every occurrence of the macro name is replaced by its token string.
#define PI            3.14159265358979323846
#define MAX_BUFFER    (4 * 1024)
#define APP_VERSION   "2.5.1"
#define TRUE          1
#define FALSE         0
#define NULL          ((void *)0)

/* Usage */
double area      = PI * r * r;
char   buf[MAX_BUFFER];
printf("Version: %s\n", APP_VERSION);

Chained Macro Expansion

Macros can reference other macros. The preprocessor rescans expansion results and applies further substitutions:
#define WIDTH    80
#define LENGTH   (WIDTH + 10)

/* LENGTH expands to (80 + 10) */
int line_len = LENGTH * 2;  /* (80 + 10) * 2 = 180 */
A macro name that appears inside its own token string is not re-expanded. Self-referential macros are safe from infinite recursion:
#define RECURSIVE  (RECURSIVE + 1)
int x = RECURSIVE;  /* expands to (RECURSIVE + 1) — stops here */

Function-Like Macros

A function-like macro accepts one or more formal parameters, which are textually substituted into the replacement list whenever the macro is invoked. The opening parenthesis must immediately follow the macro name (no space).
/* Basic arithmetic macros */
#define MAX(a, b)     ((a) > (b) ? (a) : (b))
#define MIN(a, b)     ((a) < (b) ? (a) : (b))
#define ABS(x)        ((x) < 0 ? -(x) : (x))
#define CLAMP(v,lo,hi) ((v) < (lo) ? (lo) : (v) > (hi) ? (hi) : (v))

/* Macro that generates a range cursor */
#define CURSOR(top, bottom)  (((top) << 8) | (bottom))

/* Macro to compute a random integer in [min, max] */
#define GETRANDOM(min, max) \
    ((rand() % (int)(((max) + 1) - (min))) + (min))

Why Parentheses Matter

Forgetting to parenthesize parameters or the entire expansion leads to operator precedence bugs:
/* Dangerous — missing parentheses around parameters */
#define DOUBLE_BAD(x)   x + x
int r = DOUBLE_BAD(3) * 2;  /* expands to 3 + 3 * 2 = 9, not 12 */

/* Safe — parenthesize every parameter and the whole expression */
#define DOUBLE_OK(x)    ((x) + (x))
int r = DOUBLE_OK(3) * 2;   /* expands to ((3) + (3)) * 2 = 12 */

Side-Effect Pitfall

If a macro parameter appears more than once in the token string, any argument expression with side effects is evaluated more than once:
#define MAX(a, b)  ((a) > (b) ? (a) : (b))

int i = 5;
int m = MAX(i++, 3);
/* expands to: ((i++) > (3) ? (i++) : (3))
   i is incremented TWICE — undefined behavior */
Never pass expressions with side effects (like i++, ++i, function calls that modify state) to function-like macros that use the parameter more than once. Prefer inline functions or static inline functions when side effects are possible.

Macro Expansion Rules

The MSVC preprocessor follows these rules when expanding a macro call:
  1. Argument pre-scanning: Each actual argument is fully macro-expanded before substitution into the replacement list, unless the parameter is preceded by #, followed by ##, or immediately preceded by ##.
  2. Formal parameter substitution: Each occurrence of a formal parameter in the replacement list is replaced by the (possibly pre-expanded) actual argument.
  3. Rescan: The expanded result is rescanned for further macro calls.
#define STRINGIFY(x)  #x
#define EXPAND(x)     STRINGIFY(x)
#define MY_VALUE      42

STRINGIFY(MY_VALUE)  /* → "MY_VALUE"  (no pre-scan because # suppresses it) */
EXPAND(MY_VALUE)     /* → "42"        (pre-scan expands MY_VALUE to 42 first) */

Stringizing Operator (#)

When a formal parameter is preceded by # in the replacement list, the preprocessor converts the actual argument into a string literal. The result is surrounded by double quotes, and any embedded quotes or backslashes are escaped.
#define STRINGIFY(x)  #x
#define TOSTRING(x)   STRINGIFY(x)  /* indirection trick to expand macros first */

printf("%s\n", STRINGIFY(Hello World));  /* "Hello World" */
printf("%s\n", STRINGIFY(3 + 4));        /* "3 + 4"       */

/* Stringize a predefined macro's *value* */
printf("%s\n", TOSTRING(__LINE__));      /* e.g., "27"   */
A common use case is printing both the expression and its value in assertions:
#define CHECK(expr)  do { \
    if (!(expr)) {        \
        fprintf(stderr, "Check failed: %s [%s:%d]\n", #expr, __FILE__, __LINE__); \
        abort();          \
    }                     \
} while (0)

CHECK(pointer != NULL);   /* prints: Check failed: pointer != NULL [main.c:45] */

Token-Pasting Operator (##)

The ## operator concatenates two tokens in the replacement list, forming a new single token. The operands are not macro-expanded before pasting; use an extra level of indirection if you need expansion first.
/* Build field names at compile time */
#define FIELD(prefix, num)   prefix##num

struct Point {
    int FIELD(coord, 0);   /* coord0 */
    int FIELD(coord, 1);   /* coord1 */
    int FIELD(coord, 2);   /* coord2 */
};

/* Generate unique variable names (common with __COUNTER__) */
#define CONCAT(a, b)        a##b
#define UNIQUE(base)        CONCAT(base, __COUNTER__)

int UNIQUE(temp) = 10;   /* temp0 */
int UNIQUE(temp) = 20;   /* temp1 */

Token-Pasting and Macro Arguments

If either operand of ## is a macro that should be expanded first, use an intermediate helper macro:
#define PASTE(a, b)      a##b
#define XPASTE(a, b)     PASTE(a, b)   /* forces expansion of a and b */
#define PREFIX           foo_

PASTE(PREFIX, bar)   /* → PREFIX##bar = PREFIXbar (no expansion) */
XPASTE(PREFIX, bar)  /* → foo_bar                (expansion applied) */

Variadic Macros and __VA_ARGS__

Variadic macros accept a variable number of arguments using an ellipsis (...) as the last parameter. The __VA_ARGS__ identifier in the replacement list is replaced by all the extra arguments, including the commas between them.
#include <stdio.h>

/* Logging macros with variadic arguments */
#define LOG_INFO(fmt, ...)   printf("[INFO]  " fmt "\n", ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...)  fprintf(stderr, "[ERROR] " fmt "\n", ##__VA_ARGS__)

LOG_INFO("Server started on port %d", 8080);
LOG_ERROR("File not found: %s (errno=%d)", "config.ini", errno);

/* Zero extra args */
LOG_INFO("Startup complete");   /* Works; ## suppresses trailing comma in MSVC traditional mode */

Variadic Macro Example

// variadic_macros.c
#include <stdio.h>

#define CHECK1(x, ...) if (!(x)) { printf(__VA_ARGS__); }
#define CHECK2(x, ...) if ((x))  { printf(__VA_ARGS__); }
#define CHECK3(...)    { printf(__VA_ARGS__); }
#define MACRO(s, ...)  printf(s, __VA_ARGS__)

int main(void) {
    CHECK1(0, "Condition false: %s %s\n", "arg1", "arg2");
    CHECK2(1, "Condition true:  %s %s\n", "arg1", "arg2");
    CHECK3("Always prints: %d\n", 42);
    MACRO("Hello, %s!\n", "world");
    return 0;
}
The C standard requires at least one argument to the ellipsis. The traditional MSVC preprocessor suppresses a trailing comma when no extra arguments are passed (using the ##__VA_ARGS__ extension). The conformant preprocessor (/Zc:preprocessor) does not suppress the trailing comma; use __VA_OPT__(,) (C++20/future C) for strictly conformant code.

Predefined Macros

MSVC predefines numerous macros automatically. They fall into three groups: standard macros, MSVC-specific macros, and platform/architecture macros.

Standard Predefined Macros

/* Always defined */
__FILE__    /* Current source filename as a string literal            */
__LINE__    /* Current line number as an integer constant             */
__DATE__    /* Compilation date: "Mmm dd yyyy" (e.g., "Jan  5 2025") */
__TIME__    /* Compilation time: "hh:mm:ss"    (e.g., "14:32:05")    */

/* Defined within a function body only */
__func__    /* Undecorated function name (C99/C11 standard identifier) */

/* Example */
#define WHERE()  printf("%s:%d in %s()\n", __FILE__, __LINE__, __func__)
/* __cplusplus: defined only when compiling as C++ */
#ifdef __cplusplus
    /* C++ mode */
#endif

/* __STDC_VERSION__: defined when compiling as C with /std:c11 or /std:c17 */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201710L
    /* C17 or later */
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
    /* C11 or later */
#endif

/* __STDC__: defined as 1 when /Zc:__STDC__ or /Za is used */

MSVC-Specific Predefined Macros

_MSC_VER encodes the major and minor version of the MSVC compiler as a four-digit integer: MMmm where MM is the major version and mm is the minor version.
_MSC_VER valueVisual Studio version
1800Visual Studio 2013
1900Visual Studio 2015
19101916Visual Studio 2017
19201929Visual Studio 2019
1930+Visual Studio 2022
/* Require at least Visual Studio 2019 */
#if _MSC_VER < 1920
#error This project requires Visual Studio 2019 or later.
#endif

/* Full version number */
printf("Compiler: %d\n",   _MSC_VER);       /* e.g., 1939     */
printf("Full ver: %d\n",   _MSC_FULL_VER);  /* e.g., 193900000 */
printf("Build:    %d\n",   _MSC_BUILD);     /* patch build number */
/* Windows target (32-bit or 64-bit) */
#ifdef _WIN32
    /* All Windows builds (includes _WIN64) */
#endif

#ifdef _WIN64
    /* 64-bit Windows only */
#endif

/* Architecture */
#ifdef _M_X64
    /* x86-64 (AMD64) target */
#endif

#ifdef _M_ARM64
    /* ARM64 target */
#endif

#ifdef _M_IX86
    /* 32-bit x86 target; value indicates optimization (300, 400, 500, 600, 700) */
#endif

/* Example: architecture-specific code */
#if defined(_M_X64) || defined(_M_ARM64)
#  define PLATFORM_64BIT 1
#else
#  define PLATFORM_64BIT 0
#endif
/* Debug build — defined when /MDd, /MTd, or /LDd is used */
#ifdef _DEBUG
    printf("Running debug build\n");
#endif

/* DLL runtime — defined when /MD or /MDd is used */
#ifdef _DLL
    printf("Using DLL runtime\n");
#endif

/* __COUNTER__ — unique integer, incremented every use */
#define UNIQUE_ID()  __COUNTER__
int id1 = UNIQUE_ID();  /* 0 */
int id2 = UNIQUE_ID();  /* 1 */
int id3 = UNIQUE_ID();  /* 2 */

/* __TIMESTAMP__ — last modification time of the source file */
printf("Source modified: %s\n", __TIMESTAMP__);

Complete Practical Example

The following example demonstrates several macro features working together:
#include <stdio.h>
#include <stdlib.h>

/* Logging with file/line info */
#define LOG(level, fmt, ...) \
    fprintf(stderr, "[" level "] %s:%d: " fmt "\n", \
            __FILE__, __LINE__, ##__VA_ARGS__)

#define LOG_DEBUG(fmt, ...)  LOG("DEBUG", fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...)   LOG("WARN",  fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...)  LOG("ERROR", fmt, ##__VA_ARGS__)

/* Safe assertion with message */
#define REQUIRE(expr, msg) do {                                    \
    _Pragma("warning(suppress: 4127)")                             \
    if (!(expr)) {                                                 \
        LOG_ERROR("Requirement failed: %s%s", #expr, msg);     \
        abort();                                                   \
    }                                                              \
} while (0)

/* Build info */
#define BUILD_INFO() \
    "Built with MSVC " TOSTRING(_MSC_VER) " on " __DATE__ " " __TIME__
#define TOSTRING(x)   _TOSTRING(x)
#define _TOSTRING(x)  #x

int divide(int a, int b) {
    REQUIRE(b != 0, "denominator must not be zero");
    return a / b;
}

int main(void) {
    printf("%s\n", BUILD_INFO());
    LOG_DEBUG("Starting computation");
    int result = divide(10, 2);
    LOG_DEBUG("Result: %d", result);
    divide(5, 0);   /* triggers REQUIRE → abort */
    return 0;
}

Redefining Macros

You can redefine a macro only if the new definition is identical to the original. Attempting to redefine with a different value produces a warning. Use #undef first if you need a different definition:
#define SCALE 10
/* ... */
#undef  SCALE
#define SCALE 20   /* OK — redefinition after #undef */

/* Redefine with identical token string — allowed (no warning) */
#define SCALE 20
#define SCALE 20   /* OK */

/* Redefine with different token string — warning C4005 */
#define SCALE 20
#define SCALE 30   /* warning: 'SCALE' macro redefinition */

See Also

Build docs developers (and LLMs) love