Skip to main content

C++

Toolchain on Windows: VS Code + MSYS2 UCRT64 + CMake

UCRT64 is the MSYS2 environment to pick for new work: it links against the Universal C Runtime that ships with Windows 10 and later, rather than the legacy msvcrt.dll used by the MINGW64 environment.

Install the toolchain

Install msys2-x86_64-*.exe and VSCodeUserSetup-x64-*.exe, then from the MSYS2 UCRT64 shell:

pacman -Syu # update first; may ask to reopen the shell
pacman -S mingw-w64-ucrt-x86_64-toolchain
pacman -S mingw-w64-ucrt-x86_64-cmake
pacman -S mingw-w64-ucrt-x86_64-ninja

The toolchain group installs:

  • mingw-w64-ucrt-x86_64-binutils
  • mingw-w64-ucrt-x86_64-crt-git
  • mingw-w64-ucrt-x86_64-gcc
  • mingw-w64-ucrt-x86_64-gdb
  • mingw-w64-ucrt-x86_64-gdb-multiarch
  • mingw-w64-ucrt-x86_64-headers-git
  • mingw-w64-ucrt-x86_64-libmangle-git
  • mingw-w64-ucrt-x86_64-libwinpthread-git
  • mingw-w64-ucrt-x86_64-winpthreads-git
  • mingw-w64-ucrt-x86_64-make
  • mingw-w64-ucrt-x86_64-pkgconf
  • mingw-w64-ucrt-x86_64-tools-git
  • mingw-w64-ucrt-x86_64-winstorecompat-git

Install CMake and Ninja from the same mingw-w64-ucrt-x86_64- prefix. Mixing an MSYS2 package with a separately installed Windows CMake or with another environment's GCC is the usual root cause of generator and ABI errors.

Add the toolchain to PATH

# Opens the environment variables editor
rundll32 sysdm.cpl,EditEnvironmentVariables

Add C:\msys64\ucrt64\bin to PATH. Do not add C:\msys64\usr\bin — it contains MSYS2's POSIX-emulation copies of find, sort and similar, which shadow the Windows ones and break unrelated tooling.

Open a new terminal and verify:

gcc --version
cmake --version
where gcc

Configure VS Code

Install the extensions CMake Tools and C/C++ (both from Microsoft).

settings.json:

{
"cmake.cmakePath": "C:\\msys64\\ucrt64\\bin\\cmake.exe",
"cmake.generator": "Ninja"
}

Make MSYS2 the default terminal

settings.json:

{
"terminal.integrated.profiles.windows": {
"MSYS2 UCRT64": {
"path": [
"C:\\msys64\\usr\\bin\\bash.exe",
"${env:windir}\\System32\\cmd.exe"
],
"args": [
"--login",
"-i"
],
"env": {
"MSYSTEM": "UCRT64",
"CHERE_INVOKING": "1",
"MSYS2_PATH_TYPE": "inherit"
}
}
},
"terminal.integrated.defaultProfile.windows": "MSYS2 UCRT64"
}

MSYSTEM must match the environment you installed into. Setting it to MINGW64 while the packages live under ucrt64 puts the wrong bin directory first in PATH, and the resulting binaries mix runtimes. CHERE_INVOKING=1 keeps the shell in the current directory instead of jumping to $HOME.

First project

Press F1, type >, then cmake: and choose Quick Start. Pick the GCC kit from the UCRT64 directory when prompted.

That produces a CMake project template. Press Debug — if the toolchain is set up correctly, execution stops at the breakpoint on the first line of main().

Ninja is used as a default generator

The error means CMake selected Ninja but cannot use it. Check, in order:

  • Ninja is installed in the same environment (pacman -S mingw-w64-ucrt-x86_64-ninja).
  • cmake and gcc both resolve inside C:\msys64\ucrt64\bin (where cmake, where gcc). A Windows-installed CMake paired with an MSYS2 GCC is the most common mismatch.
  • The build directory does not carry a stale cache. Delete build/ — or run CMake: Delete Cache and Reconfigure — after changing generator or kit, because CMakeCache.txt pins the generator it was created with.

Falling back to "cmake.generator": "MinGW Makefiles" works, at the cost of slower builds.

Compiler flags

RTTI (Run-Time Type Identification)

-frtti # enable (GCC and Clang default for C++)
-fno-rtti # disable
  • RTTI gives a standard way to determine an object's type at run time, through typeid and dynamic_cast.
  • The underlying representation is vendor-specific, so type information cannot be exchanged across compilers or across an ABI boundary.
  • It only applies to polymorphic classes — a class with at least one virtual function. dynamic_cast on a non-polymorphic type is a compile error.
  • Mixing translation units built with and without RTTI produces undefined reference to typeinfo at link time. The flag has to be consistent across the whole binary, including static libraries.
  • Embedded projects often disable it to save flash. -fno-exceptions is usually turned off alongside for the same reason; both must then be applied to every object in the image.

Selected FPU does not support instructions

The floating-point ABI and the FPU model must match the target core, and must be identical for every object file and every prebuilt library that gets linked.

# Cortex-M4F and Cortex-M7 with single-precision FPU
-mfloat-abi=hard -mfpu=fpv4-sp-d16

# Cortex-M7 with double-precision FPU
-mfloat-abi=hard -mfpu=fpv5-d16

# Cortex-M33
-mfloat-abi=hard -mfpu=fpv5-sp-d16

-mfloat-abi selects how floating-point values are passed:

ValueMeaning
softSoftware floating point; no FPU instructions emitted.
softfpFPU instructions, but arguments passed in integer registers — link-compatible with soft libraries.
hardFPU instructions and arguments in FPU registers. Fastest, and incompatible with soft/softfp objects.

Check the specific values against the reference manual for your part; the combinations above are the common ones, not a complete list. On STM32 the FPU also has to be enabled at run time (CMSIS does this in SystemInit when __FPU_PRESENT is set), otherwise the first FPU instruction raises a UsageFault.