C++ Development Environment Setup: Installing a Compiler and Building Hello World on Windows, macOS, and Linux
[C++ Hands-On Guide #1] Setting Up a C++ Development Environment
Previous: Guide #0: What is C++? covers history, ecosystem, pros/cons.
Requirements: Windows, macOS, or Linux with MSVC, MinGW (GCC), Xcode (Clang), or system packages (g++, build-essential). No extra libraries are required for Hello World.
After this article: You can install a compiler on your OS and run a first program end to end.
To learn C++, start by installing a toolchain. This guide walks through Windows, macOS, and Linux. At first, “install one compiler and run g++ once in a terminal” is enough. IDEs and build systems (CMake #4) are covered later—the goal here is turning a .cpp file into an executable.
Setting up C++ is harder than setting up Python or JavaScript for a structural reason: there is no single official implementation. Three major compilers (GCC, Clang, MSVC) exist side by side, each with its own standard library, command-line flags and, on Windows, its own binary format for libraries. An IDE is just a front end that calls one of them. Most first-day problems — “command not found”, an IDE that underlines valid code, a program that runs from the IDE but not from Explorer — come from the editor, the terminal and the compiler not agreeing on which toolchain is in use. Getting the compiler working in a plain terminal first gives you a known-good baseline before adding anything on top.
Common problem scenarios
“g++ is not recognized”
Cause: Compiler not installed, or PATH does not include the compiler bin directory (common on Windows after MinGW/MSYS2).
Fix: See Windows setup for PATH.
The shell only finds programs in the directories listed in PATH, and the variable is read when the terminal starts. Editing PATH in System Properties does nothing for terminals — or VS Code windows — that were already open, so after changing it close and reopen them; where g++ (Windows) or which g++ (Unix) shows which binary, if any, is being found. If where g++ lists several paths, for example an old MinGW install plus MSYS2, the first one wins, which explains “I installed GCC 14 but g++ --version says 8.1”.
“Visual Studio is too heavy”
Cause: VS bundles IDE, debugger, and SDKs (multi‑GB install).
Fix: Use MinGW via MSYS2 for a lighter GCC toolchain, or WSL for Linux g++.
“cl is not recognized” (MSVC)
Cause: cl only works in a Developer Command Prompt or after vcvarsall.bat sets the environment.
Fix: Start Developer Command Prompt for VS from the Start menu.
This is by design rather than an install problem. MSVC needs several environment variables — PATH for cl.exe and link.exe, INCLUDE for the C++ and Windows SDK headers, LIB for the libraries — and these differ per target architecture (x86, x64, ARM64). Visual Studio does not put them into the global environment, so several versions can coexist; the Developer Command Prompt runs vcvarsall.bat to set them for that one window. A related symptom is cannot open include file: 'iostream' from a plain command prompt where cl happens to be on PATH but INCLUDE is not set.
Linker errors when compiling with gcc instead of g++
Cause: gcc hello.cpp does compile the file as C++ (it looks at the extension), but it links like a C program and does not add the C++ standard library. The error is therefore not a missing header but a link failure such as undefined reference to 'std::cout' or undefined reference to 'std::ios_base::Init::Init()'.
Fix: Use g++ (or clang++), which links libstdc++/libc++ automatically. A genuine fatal error: iostream: No such file or directory usually means the C++ part of the toolchain is not installed (for example only the C compiler package) or the file was saved with a .c extension.
“On macOS, g++ shows Apple Clang”
Cause: /usr/bin/g++ is a shim that runs Apple Clang. That is normal—use Clang for development on macOS. It accepts GCC-style flags, so most tutorials work unchanged, but GCC-only extensions such as <bits/stdc++.h> do not exist in Clang’s library.
“Local build works; CI fails”
Cause: Compiler version, standard flags, or platform-specific libraries differ.
Fix: Pin toolchain versions; see Production tips. A frequent specific case is a missing #include: one standard library header may happen to include another (so std::string works after only <iostream> with one compiler), while a different compiler or version does not, and the build breaks with “no member named …” or “was not declared in this scope”. Include every header you use directly.
“Missing libgcc_*.dll” (MinGW)
Cause: Runtime DLLs not beside the .exe or on PATH.
Fix: Add C:\msys64\mingw64\bin to PATH, or link statically (-static-libgcc -static-libstdc++) when distributing.
This usually appears only when the program is started from Explorer or on another machine, because the terminal you built in already has the MinGW bin directory on PATH. The typical messages name libgcc_s_seh-1.dll, libstdc++-6.dll or libwinpthread-1.dll. A subtler variant is a program that starts but crashes immediately with “The procedure entry point … could not be located”: another program on PATH ships a different libstdc++-6.dll, and Windows loads that one instead. Static linking avoids both.
“VS Code IntelliSense is broken”
Cause: Wrong compilerPath in c_cpp_properties.json.
Fix: See IDE setup.
IntelliSense is a separate engine from the compiler: it asks the configured compiler for its include paths and default standard, then parses your code itself. If compilerPath points to a compiler that does not exist, or to MSVC while you build with MinGW, you get red squiggles on #include <vector> or on C++17 features even though the build succeeds. The reverse also happens — no squiggles, yet the build fails — when the build task uses a different compiler than IntelliSense.
Why the environment matters
C++ is compiled: unlike Python or JavaScript, you must run a compiler to produce a native binary. After you edit code, recompile to see changes.
Three pillars:
- Compiler — translates source to machine code (GCC, Clang, MSVC).
- Build tools — scale to many files (CMake is the common cross-platform choice).
- Editor/IDE — edit, navigate, debug (VS Code, Visual Studio, CLion, …).
This article focuses on installing the compiler first; editors and CMake plug in once the toolchain is known. See #4 CMake and #3 VS Code for wiring paths to
g++/clang++.
flowchart LR
A[Source .cpp] --> B[Preprocessor]
B --> C[Compiler]
C --> D[Assembler]
D --> E[Linker]
E --> F[Executable]
The diagram explains why errors come in two kinds. The preprocessor and compiler work on one .cpp file at a time: they expand #includes, check syntax and types, and produce an object file (.o or .obj). Errors from this stage carry a file name and line number (“expected ’;’ before …”). The linker then combines all object files and libraries into one executable and resolves every function call to an actual definition. Its errors (“undefined reference to …”, MSVC’s LNK2019: unresolved external symbol) have no line number, because the problem is not in any single line — something is declared but its definition was never handed to the linker. Knowing which stage an error comes from is the fastest way to know where to look.
Compiler selection guide
| Compiler | OS | Notes |
|---|---|---|
| MSVC | Windows | Best VS integration; strong Windows debugging |
| GCC | Linux | Ubiquitous; excellent standards support |
| Clang | macOS / cross-platform | Fast compiles; excellent diagnostics |
Windows: MSVC for Windows-only/DirectX stacks; MinGW to mirror Linux/CI with g++.
macOS: Clang via Xcode Command Line Tools.
Linux: GCC via build-essential.
For learning, the differences between the three compilers matter much less than consistency. All three support C++17 fully and most of C++20 in current releases. The practical differences are in error messages (Clang’s are usually the most readable, and GCC’s have improved a lot), the debugger they pair with (Visual Studio’s debugger for MSVC, gdb for GCC, lldb for Clang), and binary compatibility: libraries compiled with MSVC cannot be linked into a MinGW program and vice versa, because they use different C++ ABIs and runtime libraries. That last point is why the advice is “one toolchain per project” — mixing a prebuilt MSVC library into a MinGW build produces pages of unresolved-symbol errors that no include path fix will solve.
Windows setup
Option A — Visual Studio (MSVC)
- Download Visual Studio Community from visualstudio.microsoft.com.
- Select workload “Desktop development with C++” (MSVC, Windows SDK, optional CMake tools).
- Open Developer Command Prompt for VS 2022 and run
clto verify the toolchain is onPATH. Quick test:
cd %USERPROFILE%\Desktop
cl /EHsc hello.cpp
hello.exe
Option B — MSYS2 + MinGW (GCC)
- Install from msys2.org.
- In the MSYS2 terminal, run
pacman -Syu(possibly twice), then install the toolchain:
pacman -S mingw-w64-x86_64-gcc
- Add C:\msys64\mingw64\bin to PATH (System Properties → Environment Variables). Open a new CMD/PowerShell and verify:
g++ --version
Optional: pacman -S mingw-w64-x86_64-gdb mingw-w64-x86_64-make
MSYS2 opens several different shells (MSYS, MINGW64, UCRT64, CLANG64), and the package prefix must match the environment you use: mingw-w64-x86_64-* packages go into mingw64, while MSYS2 now recommends the UCRT64 environment (mingw-w64-ucrt-x86_64-gcc, installed under C:\msys64\ucrt64\bin), which uses Microsoft’s newer Universal C Runtime. Either works for learning; what breaks things is installing the compiler for one environment and adding the other environment’s bin directory to PATH. Also avoid the plain gcc package without a prefix — that is the MSYS environment’s compiler, which builds programs that depend on msys-2.0.dll rather than native Windows programs.
Option C — WSL (Ubuntu + g++)
wsl --install
Then inside Ubuntu:
sudo apt update && sudo apt install build-essential
g++ --version
Use Remote - WSL in VS Code for a smooth edit-on-Windows, build-on-Linux flow.
WSL gives you a real Linux toolchain, which is the closest match to most CI servers and to Linux-focused tutorials. Two caveats: the programs you build are Linux binaries and do not run as .exe files on Windows, and keeping the project under /mnt/c/... makes builds much slower because every file access crosses the Windows/Linux boundary. Keep source code inside the Linux file system (for example ~/projects) and open it from VS Code through the WSL extension.
macOS setup
Xcode Command Line Tools (recommended)
xcode-select --install
clang++ --version
g++ --version # usually Clang
Full Xcode
Install from the Mac App Store if you need iOS/macOS app tooling; select the toolchain with xcode-select -s if required.
Real GCC (optional)
brew install gcc
# installs a versioned binary such as g++-14; plain g++ is still Apple Clang
The Command Line Tools are enough for everything in this series; the full Xcode app is only needed for Apple platform development. After a major macOS upgrade, builds sometimes fail with xcrun: error: invalid active developer path — the upgrade removed the tools, and running xcode-select --install again fixes it. Homebrew GCC is useful mainly when you need GCC-specific behavior; it uses its own libstdc++, so do not mix libraries built with it and with Apple Clang in the same program.
Linux setup
Debian/Ubuntu
sudo apt update
sudo apt install build-essential cmake gdb
g++ --version
Fedora / RHEL
sudo dnf groupinstall "Development Tools"
sudo dnf install gcc-c++
Arch
sudo pacman -S base-devel
Alpine
apk add build-base
Distribution packages give you the compiler version that distribution shipped with, which can lag behind by a year or more on long-term-support releases. That is fine for learning, and the stability is the reason servers use them. If you need a newer version for a specific C++20 or C++23 feature, Ubuntu’s ubuntu-toolchain-r/test PPA or distribution packages such as g++-13 let you install a newer compiler alongside the default one and select it explicitly with CXX=g++-13. Alpine uses musl instead of glibc, so binaries built there do not run on other distributions and vice versa — relevant once you start building in Docker.
First program
Create hello.cpp:
// After paste: g++ hello.cpp -o hello && ./hello (MinGW/macOS/Linux)
#include <iostream>
int main() {
std::cout << "Hello, C++!" << std::endl;
return 0;
}
MSVC (Developer Prompt):
cl /EHsc hello.cpp
hello.exe
GCC/Clang:
g++ -std=c++17 hello.cpp -o hello
./hello
A few things in these commands are easy to overlook. /EHsc tells MSVC to enable standard C++ exception handling; without it, cl prints warning C4530 for any code that uses <iostream>. With g++, -o hello names the output — omit it and you get a.out (or a.exe on Windows). The ./ in ./hello is required on Linux and macOS because the current directory is not on PATH there; typing just hello gives “command not found” even though the file is right there. On Windows, a program started by double-clicking closes its console window the moment main returns, so “the window flashes and disappears” is normal — run it from a terminal instead of adding system("pause").
Once Hello World works, I would add -Wall -Wextra to every compile from the start. Beginners often avoid warnings because they look like noise, but many of the bugs that take hours later — an uninitialized variable, if (x = 5) instead of ==, comparing signed and unsigned integers — are exactly what those flags report on the first compile.
IDE setup
- VS Code: Install C/C++ (Microsoft). Set
compilerPathin.vscode/c_cpp_properties.json. See #3 VS Code. - Visual Studio: File → New → Console App; set C++ standard in project properties; Ctrl+Shift+B to build, F5 to debug.
In VS Code, three files do different jobs and are easy to confuse: c_cpp_properties.json configures IntelliSense only, tasks.json defines the build command that actually runs the compiler, and launch.json configures the debugger. Changing the C++ standard in one of them does not change the others. In Visual Studio, project properties are stored per configuration and platform (Debug/Release × x64/Win32), so a setting changed only for Debug appears to “not work” after switching to Release.
Build tools (basics)
Makefile (Linux/macOS):
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -g
app: main.cpp utils.cpp
$(CXX) $(CXXFLAGS) main.cpp utils.cpp -o app
CMake (cross-platform) — see #4:
cmake_minimum_required(VERSION 3.10)
project(MyApp)
set(CMAKE_CXX_STANDARD 17)
add_executable(myapp main.cpp utils.cpp)
mkdir build && cd build
cmake ..
cmake --build .
The Makefile recipe line must start with a real tab character; an editor that converts tabs to spaces produces Makefile:4: *** missing separator. Stop.. This minimal Makefile also recompiles everything whenever any file changes, which is fine for two files and slow for fifty — the reason larger projects use CMake, which generates build files that track dependencies per file. CMake itself does not compile anything: cmake .. generates build files for a native tool (Makefiles, Ninja or a Visual Studio solution), and cmake --build . runs that tool. Building in a separate build directory keeps generated files out of the source tree, so deleting build is always a safe way to start over.
Online compilers
Use Wandbox, Compiler Explorer, OnlineGDB, or cpp.sh for quick experiments. For real projects, prefer a local toolchain.
Common errors (summary)
- g++: command not found → install compiler / fix PATH.
- undefined reference to
std::cout→ link withg++, notgcc. clnot found → use Developer Command Prompt.cannot open output file hello.exe: Permission denied(Windows) → the previous build is still running or held by antivirus; close it and rebuild. On Unix, “Permission denied” when running usually means the file is on anoexecmount or was copied without the execute bit (chmod +x).- undefined reference → link all
.cpp/ libraries. - bits/stdc++.h → non-portable; prefer standard headers.
Best practices
- Pin -std=c++17 (or newer) consistently.
- Enable -Wall -Wextra.
- Keep build/ out of source control; add to
.gitignore.
Troubleshooting
flowchart TD
A[Compile error] --> B{Compiler on PATH?}
B -->|No| C[Install / PATH]
B -->|Yes| D{Header error?}
D -->|Yes| E[Use g++, -std=c++17]
D -->|No| F{Link error?}
F -->|Yes| G[List all objects / -l libs]
Production tips
- CI: Match compiler versions locally and on runners; use reproducible Docker images.
- Release flags:
-O3 -DNDEBUG(with measurements); Debug:-O0 -g.
Closing
Pick one toolchain per project, verify with g++ --version / clang++ --version / cl, then build Hello World. Next: Compiler comparison or #2-1 compiler basics.
Frequently Asked Questions (FAQ)
Q. My compiler is recent but it rejects C++17 features. What is wrong?
A. Older GCC and Clang releases default to an older language standard, so pass it explicitly, for example g++ -std=c++17 main.cpp, or /std:c++17 for MSVC. IDEs keep their own setting (in Visual Studio it is under C/C++ > Language > C++ Language Standard), so a build that works in the terminal can still fail inside the IDE until you change it there too.