Install a C++ toolchain on Windows
Windows has no compiler of its own, so the first decision is which one to install. Three setups give a complete C++ toolchain, and each one has a different compiler underneath: MSYS2 with MinGW-w64, Visual Studio Build Tools, and WSL with a Linux distribution.
This lesson compares them, installs the two native ones step by step, and builds the same source with the flags of each compiler. Every program here except windows_only.cpp was compiled and run on the Linux machine described at the end of the page; windows_only.cpp stops at its first include, as its section shows. The Windows commands are the same words with .exe at the end, and neither MSYS2 nor cl.exe was run for this page.
Three ways to get a compiler
MSYS2 with MinGW-w64 installs GCC, the compiler this course uses on Linux, so every warning flag and every -std value in the course works unchanged. Visual Studio Build Tools installs MSVC, the compiler behind Visual Studio and most Windows binaries. WSL runs a Linux distribution inside Windows and gives the Linux toolchain of lesson 001.
All three compile the same C++, and the preprocessor says which one is at work. The macros _WIN32, __GNUC__ and _MSC_VER are set by the compiler itself, so a program can report its own toolchain without a build script.
#include <iostream>
int main()
{
#if defined(_WIN32)
std::cout << "target: Windows\n";
#else
std::cout << "target: not Windows\n";
#endif
#if defined(__GNUC__)
std::cout << "compiler: GCC " << __VERSION__ << '\n';
#elif defined(_MSC_VER)
std::cout << "compiler: MSVC " << _MSC_VER << '\n';
#endif
std::cout << "standard: " << __cplusplus << '\n';
}
g++ -std=c++23 -Wall -Wextra identity.cpp -o identity
./identity
target: not Windows
compiler: GCC 15.2.0
standard: 202302
MSYS2 and MinGW-w64
MSYS2 is a package manager and a Unix shell for Windows. Install it from msys2.org, open the UCRT64 shell it adds to the Start menu, and run pacman -S mingw-w64-ucrt-x86_64-gcc. The package places g++.exe in the ucrt64/bin folder of the MSYS2 install, and Windows finds it from any directory once that folder is on PATH.
The source is ordinary C++ and knows nothing about Windows. The commands below are the ones this lesson ran on Linux; on Windows they are the same words, and the output file is hello.exe.
Keep the UCRT64 shell for building: it sets PATH for that session, which is the quickest way to try the compiler before touching the system environment variables.
#include <iostream>
#include <string>
int main()
{
std::string target = "a C++ compiler";
std::cout << "Hello from " << target << "!\n";
}
g++ -std=c++23 -Wall -Wextra hello.cpp -o hello
./hello
Hello from a C++ compiler!
Visual Studio Build Tools
The Build Tools are a smaller install than the full IDE: the installer asks for the Desktop development with C++ workload, and that brings cl.exe, the Windows SDK and the linker. Visual Studio itself is optional; the compiler is the part that matters here.
cl.exe is not on PATH by default. The Developer Command Prompt for VS opens a console where it is, and the same prompt sets the environment variables the linker needs, which is why a plain cmd window often fails where the developer prompt works.
The flag names differ from GCC: /std:c++latest or /std:c++20 instead of -std, /W4 instead of -Wall -Wextra, and /EHsc for standard exception handling. The program below builds with g++ here and uses nothing that cl.exe rejects; the flags above are the ones cl takes.
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words{"portable", "source", "builds", "twice"};
for (std::size_t i = 0; i < words.size(); ++i) {
if (i > 0) {
std::cout << ' ';
}
std::cout << words[i];
}
std::cout << '\n';
}
g++ -std=c++23 -Wall -Wextra portable.cpp -o portable
./portable
portable source builds twice
The two toolchains disagree on sizes
Linux on x86_64 uses the LP64 model: long and pointers are 8 bytes wide. Windows uses LLP64: pointers are 8 bytes, long stays at 4, and wchar_t is 2 bytes because it holds UTF-16 code units. A program that stores a file size in a long compiles on both systems and behaves differently on one of them.
The program prints the sizes of the machine it runs on. Run it on Windows too: the pointer size matches, the long size does not. That difference is the reason the fixed-width types int32_t and int64_t from <cstdint> exist, and why the standard library returns std::size_t and std::ptrdiff_t instead of int.
#include <cstddef>
#include <iostream>
int main()
{
std::cout << "pointer: " << sizeof(void*) << " bytes\n";
std::cout << "long: " << sizeof(long) << " bytes\n";
std::cout << "wchar_t: " << sizeof(wchar_t) << " bytes\n";
std::cout << "size_t: " << sizeof(std::size_t) << " bytes\n";
}
g++ -std=c++23 -Wall -Wextra sizes.cpp -o sizes
./sizes
pointer: 8 bytes
long: 8 bytes
wchar_t: 4 bytes
size_t: 8 bytes
Verify the install
Before writing real code, check that the install compiles and runs. The program uses the standard library the way the course does: std::vector, std::string and a sorting algorithm. If it prints the four words in order, the compiler, the headers, the standard library and the linker agree.
The same file builds with g++ -Wall -Wextra and with cl /W4 /EHsc. The commands below are the g++ ones that ran here. A missing header at this point means the package is incomplete, not that the code is wrong, and reinstalling the workload is faster than editing the source.
Once this program runs, the rest of the course works the same way on Windows: save a .cpp file, compile it with the flags of your compiler, run the .exe.
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> parts{"build", "tools", "are", "ready"};
std::sort(parts.begin(), parts.end());
for (std::size_t i = 0; i < parts.size(); ++i) {
std::cout << parts[i];
if (i + 1 < parts.size()) {
std::cout << ' ';
}
}
std::cout << '\n';
}
g++ -std=c++23 -Wall -Wextra verify.cpp -o verify
./verify
are build ready tools
Code that only builds on Windows
The first surprise when a project moves between the two toolchains is a header that exists on one of them. <windows.h> is part of the Windows SDK: it declares the Win32 API and it cannot be found on Linux, so a source that includes it stops at the first line.
The message below is what the same file prints here. The fix depends on the code: a portable project hides the Windows calls behind #ifdef _WIN32 and provides a second implementation, or keeps them in a file that is only compiled on Windows.
The mirror image is a Linux-only header, such as <unistd.h>, in a file compiled with cl.exe. Both compilers say the same thing in different words: the file is not there.
#include <iostream>
#include <windows.h>
int main()
{
std::cout << "this builds only with the Windows SDK\n";
}
g++ -std=c++23 -Wall -Wextra windows_only.cpp -o windows_only
windows_only.cpp:2:10: fatal error: windows.h: No such file or directory
2 | #include <windows.h>
| ^~~~~~~~~~~
compilation terminated.
Exercises
- Build identity.cpp with -std=c++17 and with -std=c++23. Which line changes, and why?
- Add an #elif branch for clang and check the macro it defines. Which macro would you test first?
- Compile hello.cpp with -o hello.exe on Linux. Does the file run, and what does the extension change?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
Which compiler does the MSYS2 mingw-w64 package provide?
Answer
GCC, driven as g++ from the MSYS2 shell
MinGW-w64 is a port of GCC and its binutils, and the package installs g++.
-
Why can a library built with MSVC not simply be linked into a MinGW program?
Answer
The two toolchains use different ABIs for names and layouts
Name mangling, exception handling and the standard library differ, so the object formats are not interchangeable.
-
What does the WSL option give a Windows user?
Answer
A Linux distribution with a Linux toolchain inside Windows
WSL runs a real Linux user space, so the commands of the Linux lessons work unchanged.
-
Why does this program test
_WIN32?#include <iostream> int main() { #if defined(_WIN32) std::cout << "target: Windows\n"; #else std::cout << "target: not Windows\n"; #endif #if defined(__GNUC__) std::cout << "compiler: GCC " << __VERSION__ << '\n'; #elif defined(_MSC_VER) std::cout << "compiler: MSVC " << _MSC_VER << '\n'; #endif std::cout << "standard: " << __cplusplus << '\n'; }Answer
To show which platform the build is running on
The macro is defined by the compiler on Windows, so the printed line says where the binary was built.
-
What does this program print in this container?
#include <iostream> int main() { #ifdef _WIN32 std::cout << "target: Windows\n"; #else std::cout << "target: not Windows\n"; #endif }Answer
target: not Windows
The lesson runs on Linux, where _WIN32 is not defined, so the else branch is compiled.
-
What does this program print?
#include <iostream> int main() { std::cout << "wchar_t: " << sizeof(wchar_t) << " bytes\n"; }Answer
wchar_t: 4 bytes
The program reports the size the ABI gives wchar_t on this build, four bytes.
-
What does this program print on this build?
#include <iostream> int main() { std::cout << sizeof(long) << ' ' << sizeof(int) << '\n'; }Answer
8 4
The 64-bit ABI gives long eight bytes and int four, and the program prints the two sizes.
-
What does this program print?
#include <iostream> int main() { #ifdef _WIN32 std::cout << "windows"; #else std::cout << "linux"; #endif }Answer
linux
_WIN32 is defined by the compiler on Windows; this build runs on Linux, so the else branch is compiled.
-
What does including <windows.h> produce in this container?
Answer
fatal error: windows.h: No such file or directory
The header belongs to the Windows SDK, and the Linux toolchain does not ship it.
-
Which pair of flags does the lesson use for MSVC's warning level and standard?
Answer
/W4and/std:c++latestMSVC spells its options with a slash, and its standard switch has no c++23 value in the versions the lesson names.
All the programs on this page except windows_only.cpp were built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. windows_only.cpp was compiled there too and stopped at its first line, which is the error its section shows. MSYS2, MinGW-w64, cl.exe and Visual Studio Build Tools were not run: they are Windows-only and this page was written on Linux, so the Windows commands and flags here are documentation, not transcripts.
Article author: Arthur Isaev