Skip to content
C++
Compiler
Updated 2026-05-01T00:00:00.000Z

MSVC

Microsoft Visual C++ — Windows-native compiler required for COM, WinRT, UWP, and Xbox development. Ships with Visual Studio and Build Tools.

TL;DR

MSVC is the Windows-native C++ compiler. Required for COM, WinRT, UWP, and Xbox development. Use /W4 /WX for warnings, /O2 for release optimization, and /std:c++20 or /std:c++latest for modern C++. Available free via Visual Studio Community or standalone Build Tools.

bat
REM Command line (Developer Command Prompt)
cl /std:c++20 /O2 /W4 /EHsc /Fe:myapp.exe main.cpp

REM With sanitizers (VS 2019 16.9+)
cl /std:c++20 /O1 /fsanitize=address /Zi /Fe:myapp.exe main.cpp

Standards support

StandardFlag
C++14/std:c++14
C++17/std:c++17
C++20/std:c++20
C++23 (partial)/std:c++latest

Use /std:c++20 for production; /std:c++latest to track C++23/26 features in development.

Essential flags

Warning flags

bat
/W0    REM No warnings
/W1    REM Severe warnings only
/W2    REM Significant warnings (default)
/W3    REM Production quality warnings
/W4    REM Informational warnings (recommended)
/Wall  REM All warnings including rarely-used ones (noisy)
/WX    REM Treat warnings as errors

Recommended: /W4 /WX for new projects.

Optimization flags

bat
/Od    REM No optimization (debug)
/O1    REM Minimize size
/O2    REM Maximize speed (release default)
/Ox    REM Full optimization
/GL    REM Whole-program optimization (link-time)
/LTCG  REM Link-time code generation (pair with /GL)

Exception handling

bat
/EHsc  REM Standard C++ exception handling (use this)
/EHa   REM Also catches SEH (structured exceptions)
/EHs   REM No async exceptions

Always specify /EHsc explicitly. Without it, destructors may not run during stack unwinding.

Debug information

bat
/Zi    REM PDB debug info (separate .pdb file)
/ZI    REM PDB with Edit-and-Continue support (dev only)
/Zd    REM Line numbers only

Runtime library

bat
/MD    REM Dynamic CRT (release) — use in DLLs and shared libraries
/MDd   REM Dynamic CRT (debug)
/MT    REM Static CRT (release) — for standalone executables
/MTd   REM Static CRT (debug)

All components in a project must use the same runtime. Mixing /MD and /MT causes linker errors or runtime crashes.

Sanitizers (VS 2019+)

bat
REM AddressSanitizer
cl /fsanitize=address /Zi /Od main.cpp

REM In CMake
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address")

MSVC ASan requires VS 2019 16.9+ and works on x64 only.

CMake integration

cmake
cmake_minimum_required(VERSION 3.20)
project(myapp CXX)
set(CMAKE_CXX_STANDARD 20)

add_executable(myapp main.cpp)

target_compile_options(myapp PRIVATE
  $<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /EHsc>
)

# Enable WPO/LTCG for Release
set_target_properties(myapp PROPERTIES
  INTERPROCEDURAL_OPTIMIZATION_RELEASE ON
)

Clang-cl — Clang with MSVC ABI

clang-cl is a drop-in replacement that accepts MSVC flags but uses Clang's frontend:

bat
clang-cl /std:c++20 /O2 /W4 /EHsc /Fe:myapp.exe main.cpp

Use clang-cl when you want:

  • Clang's better error messages on Windows
  • clang-tidy integration with MSVC ABI compatibility
  • The same binary ABI as MSVC (COM, WinRT compatible)

Precompiled headers

cpp
// stdafx.h (or pch.h)
#pragma once
#include <vector>
#include <string>
#include <memory>
// ... all stable headers
bat
REM Create PCH
cl /Yc"pch.h" /FpMyPCH.pch pch.cpp

REM Use PCH
cl /Yu"pch.h" /FpMyPCH.pch main.cpp

In CMake:

cmake
target_precompile_headers(myapp PRIVATE pch.h)

Common MSVC-specific extensions

cpp
// __declspec — Windows-specific attributes
__declspec(dllexport) void myFunc();  // Export from DLL
__declspec(dllimport) void myFunc();  // Import from DLL
__declspec(noinline) void slow();     // Prevent inlining
__declspec(align(16)) float data[4]; // Alignment

// Structured Exception Handling (Windows SEH)
__try {
    // code that might throw SEH
} __except(EXCEPTION_EXECUTE_HANDLER) {
    // handle
}

// Prefer standard [[nodiscard]], [[likely]] etc. over MSVC extensions

Notable MSVC quirks

/permissive- — enables strict conformance mode. Add this to catch non-portable code:

bat
cl /std:c++20 /permissive- main.cpp

Two-phase lookup: MSVC historically had broken two-phase name lookup. /permissive- fixes this. Always use it in new projects.

__int64 vs long long: On MSVC, long is 32-bit even on 64-bit Windows (LLP64 model). Use int64_t / uint64_t from <cstdint> for portable 64-bit integers.

min/max macros: Windows headers define min/max as macros. Use #define NOMINMAX before including <windows.h> or use (std::min)() with parentheses.

cpp
#define NOMINMAX
#include <windows.h>
#include <algorithm>

auto x = std::min(a, b);  // Fine

cl.exe Command Line

The cl.exe driver compiles and links in a single invocation unless you separate the steps. The Developer Command Prompt (or the vcvarsall.bat environment) must be active; without it, cl.exe cannot locate headers or libs.

cmd
REM Compile and link a single file — common starting point
cl /std:c++20 /O2 /W4 /WX /EHsc /Fe:myapp.exe main.cpp

REM Separate compile and link steps (useful for incremental builds)
cl /std:c++20 /O2 /W4 /EHsc /c main.cpp        REM → main.obj
link main.obj /OUT:myapp.exe

REM Release build with debug symbols (PDB alongside binary)
cl /std:c++20 /O2 /W4 /EHsc /Zi /DNDEBUG /Fe:myapp.exe main.cpp /link /DEBUG

REM Multiple source files
cl /std:c++20 /O2 /EHsc /Fe:myapp.exe src\main.cpp src\util.cpp src\io.cpp

Key flag reference

FlagEffect
/std:c++20Enable C++20 language features
/O1Minimize binary size
/O2Maximize speed (release default)
/OxFull optimization (superset of /O2)
/W4Informational warnings (recommended baseline)
/WXTreat all warnings as errors
/EHscStandard C++ exception handling
/MD / /MTDynamic / static CRT linkage
/ZiEmit separate PDB debug info
/DNDEBUGDefine NDEBUG (disables assert())
/Fe:<file>Set output executable name
/Fo:<file>Set output object file name
/I<dir>Add include search path
/linkPass remaining args to the linker

Runtime library flags must be consistent across all TUs and all linked static libs. Mixing /MD in your code with a static lib built with /MT produces multiply-defined symbol linker errors.

cmd
REM Verify which CRT a .lib was compiled against
dumpbin /directives mylib.lib | findstr /i "defaultlib"

MSVC-Specific Extensions

Windows development regularly requires MSVC-specific extensions that have no standard equivalent. Use them in platform-specific translation units and guard with #ifdef _MSC_VER where portability matters.

__declspec attributes

cpp
// DLL boundary declarations — the canonical Windows shared-library pattern
#ifdef MYLIB_EXPORTS
#  define MYLIB_API __declspec(dllexport)
#else
#  define MYLIB_API __declspec(dllimport)
#endif

MYLIB_API void myFunc();
MYLIB_API int  myValue;

// Other useful __declspec annotations
__declspec(noinline)   void neverInline();   // prevent inlining
__declspec(noreturn)   void die();           // hint: function never returns
__declspec(align(64))  char cacheLine[64];   // explicit alignment
__declspec(restrict)   void* myAlloc(size_t n); // returned ptr not aliased

Pragma comment lib

cpp
// Link a library without touching the build system — handy for SDK headers
#pragma comment(lib, "ws2_32.lib")    // Winsock
#pragma comment(lib, "d3d12.lib")     // Direct3D 12
#pragma comment(lib, "opengl32.lib")  // OpenGL

Forced inlining and assume

cpp
// __forceinline — ignore the compiler's cost model and always inline
__forceinline int add(int a, int b) { return a + b; }

// __assume — tell the optimizer something is always true (no runtime check)
void process(int* p, int n) {
    __assume(n > 0);          // optimizer may eliminate negative-n branches
    __assume(p != nullptr);   // eliminate null checks in the loop
    for (int i = 0; i < n; ++i) p[i] *= 2;
}

__int64 and LLP64

cpp
// MSVC on 64-bit Windows: long is still 32-bit (LLP64 data model)
long        x = 0;   // 32 bits — same as int
__int64     y = 0;   // 64 bits — MSVC extension
long long   z = 0;   // 64 bits — standard; prefer this
int64_t     w = 0;   // 64 bits — most portable; use this

Structured Exception Handling (SEH)

cpp
#include <windows.h>

void riskyOperation() {
    __try {
        int* p = nullptr;
        *p = 42;   // access violation — SEH catches this
    }
    __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
              ? EXCEPTION_EXECUTE_HANDLER
              : EXCEPTION_CONTINUE_SEARCH) {
        // handle hardware-level fault
    }
}

// __finally — guaranteed cleanup (analogous to RAII, but older)
void withCleanup(HANDLE h) {
    __try {
        // use h
    }
    __finally {
        CloseHandle(h);   // runs whether or not an exception occurred
    }
}

SEH and C++ exceptions do not mix cleanly. Use /EHa only when you need to catch SEH inside C++ catch(...), and be aware that it disables several optimizations. Prefer RAII over __finally in modern code.

C++ Standards Conformance

MSVC's conformance story has improved dramatically since VS 2017, but quirks remain. Always pair a standards flag with /permissive-.

cmd
REM Strict C++20 conformance — recommended for new projects
cl /std:c++20 /permissive- /W4 /WX /EHsc main.cpp

REM Track draft C++23 / C++26 features
cl /std:c++latest /permissive- /W4 /EHsc main.cpp

What /permissive- enables

BehaviorWithout /permissive-With /permissive-
Two-phase name lookupBroken (MSVC extension)Standard-conforming
Implicit conversion of string literal to char*AllowedError
Non-standard for-loop variable scopeExtendedStandard
Argument-dependent lookup (ADL)Partially brokenFixed
cmd
REM /permissive- is implied by /std:c++20 in VS 2019 16.8+ for most constructs,
REM but explicit is always safer:
cl /std:c++20 /permissive- main.cpp

Known remaining non-conformances (as of VS 2022 17.x)

  • constexpr evaluation limits differ from GCC/Clang in edge cases.
  • Some C++20 modules features remain incomplete; prefer header units for now.
  • Certain requires-clause interactions with SFINAE produce divergent diagnostics.
  • std::format locale behaviour has subtle differences on some Windows locales.

Check the MSVC conformance changelog when a feature behaves unexpectedly.

Sanitizers and Code Analysis

MSVC ships two complementary defect-detection systems: runtime sanitizers and static analysis. Use both; they catch different bug classes.

AddressSanitizer (/fsanitize=address)

cmd
REM Build with ASan — requires VS 2019 16.9+ and x64 target
cl /std:c++20 /fsanitize=address /Zi /Od /EHsc /Fe:myapp.exe main.cpp

REM ASan is incompatible with /RTC and incremental linking
REM Do not mix /fsanitize=address with /MT (use /MD)

ASan on MSVC intercepts heap allocations, stack out-of-bounds, use-after-free, and use-after-return. It does not detect uninitialized reads (use Valgrind or MemorySanitizer on Linux for that).

cmake
# CMake: enable ASan for Debug builds on MSVC
target_compile_options(myapp PRIVATE
  $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<CONFIG:Debug>>:/fsanitize=address /Zi>
)

Runtime checks (/RTC1)

cmd
REM /RTC1 = /RTCs + /RTCu — stack frame checks + uninitialized variable use
cl /Od /RTC1 /EHsc main.cpp

REM Individual options:
REM /RTCs — stack corruption detection (locals, return address)
REM /RTCu — use of uninitialized local variable
REM /RTCc — truncation on assignment to smaller type

/RTC is a Debug-only flag. It is incompatible with any optimization level above /Od. Adding it to Release builds is a compile error.

Static analysis (/analyze)

cmd
REM Enable Microsoft's SAL-based static analyzer
cl /analyze /std:c++20 /EHsc main.cpp

REM Quiet mode — only show actual warnings, suppress informational notes
cl /analyze /analyze:quiet main.cpp

REM Write analysis results to a log file
cl /analyze /analyze:log analysis.xml main.cpp

Annotate your APIs with SAL (Source Annotation Language) to get deeper analysis:

cpp
#include <sal.h>

// _In_, _Out_, _Inout_, _In_reads_(n) etc.
void copyBuffer(_In_reads_(n) const char* src,
                _Out_writes_(n) char* dst,
                _In_ size_t n);

The /analyze flag surfaces buffer overruns, null dereferences, and lock-order violations that ASan cannot catch at compile time.

PDB Files and Debugging

PDB (Program Database) files are the heart of the Windows debugging story. Every production binary should be built with a PDB, even in Release mode.

/Zi vs /ZI

cmd
REM /Zi — standard PDB; compatible with all optimization levels
cl /O2 /Zi main.cpp /link /DEBUG /PDB:myapp.pdb

REM /ZI — PDB with Edit-and-Continue (EnC) support
REM      Forces /Od; incompatible with /O1, /O2, /GL
cl /ZI /Od main.cpp

/Zi is the right choice for Release builds. /ZI is only useful during active development when you need to change code without rebuilding.

Linker /DEBUG flag

cmd
REM Full PDB with complete type info (required for WinDbg, VS debugger)
link /DEBUG main.obj /OUT:myapp.exe

REM /DEBUG:FULL — default; full symbol info
REM /DEBUG:FASTLINK — PDB references object files (faster link, worse debuggability)
REM /DEBUG:NONE — strip debug info (rare; strip via separate step instead)
link /DEBUG:FULL main.obj /OUT:myapp.exe /PDB:myapp.pdb

Symbol servers and _NT_SYMBOL_PATH

cmd
REM Point debuggers at Microsoft's public symbol server
set _NT_SYMBOL_PATH=srv*C:\Symbols*https://msdl.microsoft.com/download/symbols

REM Add your own PDB store alongside the public server
set _NT_SYMBOL_PATH=srv*C:\MySymbols*\\myserver\symbols;srv*C:\Symbols*https://msdl.microsoft.com/download/symbols

WinDbg integration

cmd
REM Launch WinDbg with a target and symbol path
windbg -y "srv*C:\Symbols*https://msdl.microsoft.com/download/symbols" -z myapp.exe

REM Post-mortem: open a crash dump
windbg -z crash.dmp -y "srv*C:\Symbols*https://msdl.microsoft.com/download/symbols"

WinDbg Preview (from the Microsoft Store) is the modern replacement with a UI. For automated analysis, use cdb.exe (console debugger from the WDK).

cmd
REM Automated stack trace from a dump (CI/crash reporting pipelines)
cdb -z crash.dmp -y "srv*C:\Symbols*https://msdl.microsoft.com/download/symbols" -c "!analyze -v; q"

Precompiled Headers (PCH) — Deep Dive

PCH offers the largest compile-time win in large MSVC projects, often cutting rebuild times by 50–80%. The gain is proportional to how many stable, heavyweight headers you include.

Classic stdafx.h pattern

cpp
// stdafx.h — include all stable, rarely-changed headers here
#pragma once

// Windows / CRT
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

// Standard library
#include <vector>
#include <string>
#include <memory>
#include <unordered_map>
#include <algorithm>
#include <functional>
#include <optional>
#include <span>

// Third-party (if stable)
#include <nlohmann/json.hpp>
cpp
// stdafx.cpp — the translation unit that CREATES the PCH
#include "stdafx.h"
// Nothing else
cmd
REM Step 1: build the PCH from stdafx.cpp
cl /Yc"stdafx.h" /Fp"stdafx.pch" /EHsc stdafx.cpp

REM Step 2: compile every other TU using the PCH
cl /Yu"stdafx.h" /Fp"stdafx.pch" /EHsc main.cpp util.cpp io.cpp

Modern CMake PCH

cmake
# CMake 3.16+ — no stdafx.cpp needed; CMake manages everything
add_executable(myapp main.cpp util.cpp io.cpp)

target_precompile_headers(myapp PRIVATE
  <vector>
  <string>
  <memory>
  <unordered_map>
  "pch.h"          # project-specific stable header
)

# Reuse a PCH across multiple targets (avoids recompiling it twice)
target_precompile_headers(mylib REUSE_FROM myapp)

When PCH actually helps

  • Large projects (50+ TUs) that include <windows.h>, Boost, or Qt headers.
  • Headers that are stable — change rarely or never during active development.
  • PCH hurts (or is neutral) when headers change frequently: every header modification invalidates the entire PCH and triggers a full rebuild.
cmake
# Tip: keep volatile project headers OUT of the PCH
# Only include headers that you never touch day-to-day
target_precompile_headers(myapp PRIVATE
  <windows.h>    # stable — never changes
  <vector>       # stable
  # NOT "myfeature.h" — you edit this regularly
)

MSBuild and CMake Workflows

CMake is the recommended build system for new cross-platform MSVC projects. MSBuild (.vcxproj) is the native format used by Visual Studio but is verbose to author by hand.

Generating with CMake

powershell
# Visual Studio 2022 generator — produces a .sln + .vcxproj
cmake -S . -B build -G "Visual Studio 17 2022" -A x64

# Visual Studio 2019
cmake -S . -B build -G "Visual Studio 16 2019" -A x64

# Use Ninja instead (faster builds, no IDE solution needed)
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER=cl

# Build from CMake (works regardless of generator)
cmake --build build --config Release --parallel
cmake --build build --config Debug   --parallel

Toolset selection (-T)

powershell
# Use clang-cl as the compiler but keep MSVC linker and ABI
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -T ClangCL

# Target a specific VS toolset version (useful for reproducible CI)
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -T v143

# Use an older toolset with the current IDE
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -T v141  # VS 2017 toolset

Building with MSBuild directly

cmd
REM Build a .vcxproj (single project)
MSBuild.exe myapp.vcxproj /p:Configuration=Release /p:Platform=x64 /m

REM Build a .sln (all projects)
MSBuild.exe myapp.sln /p:Configuration=Release /p:Platform=x64 /m

REM Rebuild (clean + build)
MSBuild.exe myapp.vcxproj /t:Rebuild /p:Configuration=Release /p:Platform=x64

REM Pass additional compiler flags via MSBuild property
MSBuild.exe myapp.vcxproj /p:Configuration=Release /p:AdditionalOptions="/fsanitize=address"

Automating Visual Studio builds

cmd
REM devenv.exe — full IDE build (respects project dependencies in .sln)
devenv.exe myapp.sln /Build "Release|x64"
devenv.exe myapp.sln /Clean "Release|x64"
devenv.exe myapp.sln /Rebuild "Debug|x64"

CI-friendly CMake preset

json
{
  "version": 6,
  "configurePresets": [
    {
      "name": "msvc-release",
      "generator": "Visual Studio 17 2022",
      "architecture": "x64",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "CMAKE_CXX_STANDARD": "20"
      }
    }
  ],
  "buildPresets": [
    {
      "name": "msvc-release",
      "configurePreset": "msvc-release",
      "configuration": "Release"
    }
  ]
}
powershell
# Use the preset
cmake --preset msvc-release
cmake --build --preset msvc-release

Cross-Compilation: ARM64 on x64

Visual Studio ships cross-compilers that run on x64 host machines and produce ARM64 binaries. No emulation layer is needed; the compiler itself is native x64.

Installing ARM64 cross tools

In the VS Installer, under your VS 2022 workload, check:

  • MSVC v143 — VS 2022 C++ ARM64 build tools
  • C++ ATL for latest v143 build tools (ARM64) (if needed)
  • Windows 11 SDK (ARM64-capable SDK, 10.0.22000.0 or later)

CMake cross-compilation

powershell
# Target ARM64 from an x64 host
cmake -S . -B build-arm64 -G "Visual Studio 17 2022" -A ARM64

cmake --build build-arm64 --config Release

The -A ARM64 flag selects the ARM64 platform toolset automatically. No additional flags are needed; CMake passes /arm64 to the linker.

Manual cross-compilation with vcvarsall

cmd
REM Activate the x64-to-ARM64 cross-compiler environment
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64_arm64

REM Now cl.exe produces ARM64 code
cl /std:c++20 /O2 /EHsc /Fe:myapp_arm64.exe main.cpp

ARM64EC (ARM64 + x64 interop)

powershell
# ARM64EC: ARM64 binary that can call x64 DLLs directly
cmake -S . -B build-arm64ec -G "Visual Studio 17 2022" -A ARM64EC

ARM64EC is the recommended target for migrating existing x64 Windows apps incrementally to ARM64. x64 DLLs and ARM64EC DLLs can be loaded in the same process.

Windows Driver Kit (WDK) for ARM64

cmd
REM After installing WDK and WDK VS Extension:
REM Select the ARM64 Driver target platform in the project properties,
REM or set via MSBuild:
MSBuild.exe mydriver.vcxproj /p:Configuration=Release /p:Platform=ARM64

REM Verify the output
dumpbin /headers mydriver.sys | findstr "machine"
REM Expected: machine (ARM64)

Cross-compiled binaries cannot be run on the host. Use Windows on ARM hardware, a Windows ARM64 VM (available on Azure), or the ARM64 Device Portal for testing.

Edit on GitHubUpdated 2026-05-01T00:00:00.000Z