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.
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.cppStandards support
| Standard | Flag |
|---|---|
| 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
/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 errorsRecommended: /W4 /WX for new projects.
Optimization flags
/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
/EHsc REM Standard C++ exception handling (use this)
/EHa REM Also catches SEH (structured exceptions)
/EHs REM No async exceptionsAlways specify /EHsc explicitly. Without it, destructors may not run during stack unwinding.
Debug information
/Zi REM PDB debug info (separate .pdb file)
/ZI REM PDB with Edit-and-Continue support (dev only)
/Zd REM Line numbers onlyRuntime library
/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+)
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_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:
clang-cl /std:c++20 /O2 /W4 /EHsc /Fe:myapp.exe main.cppUse 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
// stdafx.h (or pch.h)
#pragma once
#include <vector>
#include <string>
#include <memory>
// ... all stable headersREM Create PCH
cl /Yc"pch.h" /FpMyPCH.pch pch.cpp
REM Use PCH
cl /Yu"pch.h" /FpMyPCH.pch main.cppIn CMake:
target_precompile_headers(myapp PRIVATE pch.h)Common MSVC-specific extensions
// __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 extensionsNotable MSVC quirks
/permissive- — enables strict conformance mode. Add this to catch non-portable code:
cl /std:c++20 /permissive- main.cppTwo-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.
#define NOMINMAX
#include <windows.h>
#include <algorithm>
auto x = std::min(a, b); // Finecl.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.
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.cppKey flag reference
| Flag | Effect |
|---|---|
/std:c++20 | Enable C++20 language features |
/O1 | Minimize binary size |
/O2 | Maximize speed (release default) |
/Ox | Full optimization (superset of /O2) |
/W4 | Informational warnings (recommended baseline) |
/WX | Treat all warnings as errors |
/EHsc | Standard C++ exception handling |
/MD / /MT | Dynamic / static CRT linkage |
/Zi | Emit separate PDB debug info |
/DNDEBUG | Define NDEBUG (disables assert()) |
/Fe:<file> | Set output executable name |
/Fo:<file> | Set output object file name |
/I<dir> | Add include search path |
/link | Pass 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.
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
// 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 aliasedPragma comment lib
// 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") // OpenGLForced inlining and assume
// __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
// 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 thisStructured Exception Handling (SEH)
#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-.
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.cppWhat /permissive- enables
| Behavior | Without /permissive- | With /permissive- |
|---|---|---|
| Two-phase name lookup | Broken (MSVC extension) | Standard-conforming |
Implicit conversion of string literal to char* | Allowed | Error |
Non-standard for-loop variable scope | Extended | Standard |
| Argument-dependent lookup (ADL) | Partially broken | Fixed |
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.cppKnown remaining non-conformances (as of VS 2022 17.x)
constexprevaluation 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::formatlocale 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)
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: 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)
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)
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.cppAnnotate your APIs with SAL (Source Annotation Language) to get deeper analysis:
#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
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
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.pdbSymbol servers and _NT_SYMBOL_PATH
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/symbolsWinDbg integration
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).
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
// 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>// stdafx.cpp — the translation unit that CREATES the PCH
#include "stdafx.h"
// Nothing elseREM 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.cppModern CMake PCH
# 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.
# 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
# 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 --parallelToolset selection (-T)
# 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 toolsetBuilding with MSBuild directly
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
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
{
"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"
}
]
}# Use the preset
cmake --preset msvc-release
cmake --build --preset msvc-releaseCross-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
# Target ARM64 from an x64 host
cmake -S . -B build-arm64 -G "Visual Studio 17 2022" -A ARM64
cmake --build build-arm64 --config ReleaseThe -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
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.cppARM64EC (ARM64 + x64 interop)
# ARM64EC: ARM64 binary that can call x64 DLLs directly
cmake -S . -B build-arm64ec -G "Visual Studio 17 2022" -A ARM64ECARM64EC 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
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.