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.

Before the MSVC compiler translates your C source into machine code, a distinct processing stage called the preprocessor runs first. The preprocessor performs textual transformations on the source file: it expands macros, inserts the contents of included header files, conditionally includes or excludes blocks of code, and handles several other directives that begin with a # character. Understanding the preprocessor and when it runs is fundamental to writing robust, maintainable C programs in Visual Studio.

Role of the Preprocessor in the Build Pipeline

The build of a single translation unit (a .c file) proceeds in the following stages:
Source file (.c)


┌──────────────────────┐
│   Preprocessor       │  ← #include, #define, #if, #pragma, etc.
│   (textual phase)    │
└──────────────────────┘
       │  Preprocessed translation unit

┌──────────────────────┐
│   Compiler front end │  ← Lexing, parsing, semantic analysis
└──────────────────────┘
       │  Intermediate representation

┌──────────────────────┐
│   Code generator     │  ← Optimization, code emission
└──────────────────────┘
       │  Object file (.obj)

┌──────────────────────┐
│   Linker             │  ← Combines .obj files, resolves symbols
└──────────────────────┘


  Executable / DLL
The preprocessor operates on tokens — the basic lexical units of C — not on the compiled program. It can only act on text it sees; it has no understanding of C types, scopes, or run-time values.
Preprocessor directives are processed before macro expansion of surrounding code. This means a macro cannot expand into a preprocessor directive and have it recognized. For example, you cannot generate a #include path dynamically through macro substitution in the standard way.

Traditional vs. Conformant Preprocessor

MSVC ships two preprocessor implementations:
ModeEnabled ByDescription
TraditionalDefault (before VS 2019 16.5)Legacy behavior; some edge cases differ from the standard
Conformant/Zc:preprocessorFully conformant C11/C17 preprocessor
The conformant preprocessor (/Zc:preprocessor) is automatically activated when you compile with /std:c11 or /std:c17. It resolves many long-standing edge cases, particularly around variadic macro expansion, token pasting, and rescanning behavior.
/* Example that behaves differently between traditional and conformant
   preprocessors — variadic macro with no extra args */
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)

/* Traditional: trailing comma may be suppressed silently
   Conformant:  requires at least one variadic argument or ## trick */
LOG("No extra args\n");
If you are writing new C code, enable the conformant preprocessor explicitly with /Zc:preprocessor even if you are not yet targeting C11 or C17. It catches non-conformant patterns early and ensures your code is portable to other compilers.

Preprocessing Directives

The preprocessor recognizes the following directives. Each directive begins with a # as the first non-whitespace character on the line:
The #include directive inserts the contents of another file at the point where it appears. Two forms are supported:
  • Angle-bracket form <file> — searches the system include directories (set by /I and INCLUDE environment variable).
  • Quoted form "file" — searches first in the directory of the including file, then falls back to the system search path.
#include <stdio.h>          /* system/library header */
#include "my_config.h"      /* project-local header */
For the full reference on search order and nested includes, see Preprocessor Directives.
#define creates object-like macros (simple substitution) and function-like macros (parameterized substitution). #undef removes a macro definition so it can be redefined.
#define PI       3.14159265358979
#define MAX(a,b) ((a) > (b) ? (a) : (b))

double circumference = 2.0 * PI * radius;
int    bigger        = MAX(x, y);

#undef PI          /* PI is no longer defined */
#define PI 3.14    /* redefine with different precision */
Conditional directives let you include or exclude sections of code based on compile-time expressions or macro definitions. These are the cornerstone of platform-specific and configuration-driven code.
#define TARGET_WINDOWS 1

#ifdef TARGET_WINDOWS
#  include <windows.h>
#elif defined(TARGET_LINUX)
#  include <unistd.h>
#else
#  error "Unknown target platform"
#endif

#if _MSC_VER >= 1900
    /* Visual Studio 2015 or later */
    #define HAS_SNPRINTF 1
#endif
#error emits a compile-time error and halts translation. #warning emits a non-fatal diagnostic message. Both are commonly used to enforce build prerequisites:
#if !defined(__cplusplus) && !defined(_MSC_VER)
#error This header requires MSVC or a C++ compiler.
#endif

#if _MSC_VER < 1900
#warning This code requires Visual Studio 2015 or later for full support.
#endif
#line overrides the compiler’s reported line number and optionally the filename used in diagnostics. It is primarily inserted by code generators (e.g., parser generators, IDL compilers) so that error messages point to the original source:
#line 100 "generated_parser.c"
/* Errors after this point are attributed to generated_parser.c, line 100+ */
#pragma passes implementation-specific instructions to the compiler. MSVC supports dozens of pragmas covering optimization, struct alignment, warning control, header-include guards, and linker directives. See the full Pragma Directives Reference for details.
#pragma once                          /* prevent multiple inclusion */
#pragma warning(disable : 4996)       /* suppress deprecation warning */
#pragma comment(lib, "ws2_32.lib")    /* link against Winsock */
#pragma pack(push, 1)                 /* 1-byte struct alignment */

Predefined Macros

MSVC defines a rich set of macros automatically, without any #define directive in your source. These include:
/* Standard macros — always defined */
__FILE__          /* Current source file name as a string literal */
__LINE__          /* Current line number as an integer */
__DATE__          /* Compilation date as "Mmm dd yyyy" */
__TIME__          /* Compilation time as "hh:mm:ss" */
__func__          /* Current function name (C99/C11) */

/* MSVC version */
_MSC_VER          /* e.g., 1940 for VS 2022 17.10 */
_MSC_FULL_VER     /* Full version number */

/* Language mode */
__cplusplus       /* Defined only when compiling as C++ */
__STDC_VERSION__  /* e.g., 201710L when /std:c17 is used */

/* Target platform */
_WIN32            /* Defined for both 32-bit and 64-bit Windows builds */
_WIN64            /* Defined for 64-bit Windows builds */
A practical use case — writing portable code that compiles as both C and C++:
#ifdef __cplusplus
extern "C" {
#endif

/* C-linkage API declarations here */
void my_c_api(int x);

#ifdef __cplusplus
}
#endif

Inspecting Preprocessor Output

You can view the output of the preprocessor without compiling by passing /P (preprocess to file) or /EP (preprocess to stdout) to the compiler:
cl /P /std:c11 myfile.c
rem Output: myfile.i (preprocessed file)

cl /EP /std:c17 myfile.c
rem Output: preprocessed source written to stdout
This is invaluable for debugging complex macro expansions or diagnosing include-path problems.

Full Preprocessor Reference

The preprocessor is a large topic. This page provides an overview of its place in the build process. For detailed coverage of each directive, macro operator, and pragma, see:
  • Preprocessor Directives#include, #define, #undef, #if, #error, #line, #pragma
  • Macros (C/C++) — Object macros, function macros, token-pasting, stringizing, __VA_ARGS__, predefined macros
  • Pragma Directives#pragma once, #pragma comment, #pragma warning, #pragma pack, and more

See Also

Build docs developers (and LLMs) love