# C Development Windows Native Setup Adding C development on top of a Windows 11 machine already configured per [[General_Development_Windows_Native_Setup]]. Most of the stack carries over — Windows Terminal, PowerShell 7, Starship, Git, GitHub CLI, VS Code, Claude Code, and the PSReadLine setup all work identically. This guide only covers the C-specific additions, and everything installs the same way on **ARM64 (Snapdragon, Surface Pro 11, Dev Kit 2023)** and **x64 (Intel/AMD)**. It's the Windows-native counterpart to [[C_Development_Ubuntu_Setup]] and [[C_Development_Mac_Tahoe_Setup]]. > [!tip] AI assistance is optional > The reference to Claude Code above assumes you completed Part 7 of the General guide. If you skipped it or use a different AI tool, this guide's instructions still work — the language toolchain doesn't depend on any AI being present. > [!info] Coexistence > C, Python, Ruby, Go, and Rust can all live on the same Windows machine without interfering. Each language's toolchain installs to its own prefix: `uv` under `%USERPROFILE%\.local\`, Go under `%USERPROFILE%\go\`, Rust under `%USERPROFILE%\.rustup\` and `%USERPROFILE%\.cargo\`, the MSVC toolchain under `C:\Program Files\Microsoft Visual Studio\...`. No conflicts. > [!warning] This is the longest language guide > Unlike Linux (where `build-essential` is one apt package) and macOS (where Xcode Command Line Tools is one xcode-select command), Windows has **nothing C-related preinstalled**. There's no system compiler, no system debugger, no system headers. The C toolchain install is a multi-gigabyte download from Microsoft. Plan for ~30 minutes of install time before you start writing code. Once it's done, the day-to-day workflow is identical to Linux and Mac. --- ## What you already have Nothing C-related. Windows ships without a system C compiler — none of `cl.exe`, `gcc.exe`, or `clang.exe` exist on a fresh Windows 11 install. There are no system C headers, no static libraries, no debugger. Everything is added by this guide. Verify what you don't have: ```powershell Get-Command cl, gcc, clang -ErrorAction SilentlyContinue # Should print nothing ``` > [!info] Windows has no system compiler — by design > macOS bundles Apple Clang with Xcode Command Line Tools. Ubuntu has `gcc` one `sudo apt install build-essential` away. Windows treats the C compiler as a developer tool you opt into — most Windows users never need one. The upside is the installer is comprehensive and self-contained; the downside is the upfront commitment. --- ## What to add The C toolchain on Windows has three independent layers. All three are needed: 1. **The compiler + Windows SDK** — Microsoft's `cl.exe` (MSVC), plus optionally `clang-cl.exe` (Clang in MSVC-compatible mode) and `clangd` for editor intelligence. These ship via the **Visual Studio Build Tools** installer. 2. **The build system** — CMake + Ninja. These can either ship bundled with the Build Tools (via the "C++ CMake tools" component) or be installed independently via winget. 3. **The debugger** — `cppvsdbg` (the Visual Studio debugger, used by the Microsoft C/C++ VS Code extension) for MSVC binaries, or `lldb-dap` for clang-cl binaries. These come bundled with the Build Tools and the Clang toolset respectively. ### Compiler choice: MSVC + clang-cl (recommended) On Windows, **MSVC (`cl.exe`) is the native compiler** — the one Microsoft ships, the one the Windows SDK is calibrated against, and the one with the best AddressSanitizer support on both x64 and ARM64. **clang-cl** is Clang configured to accept MSVC's command-line flags and link against the Microsoft C runtime; it's useful as an alternative compiler and as the backend for `clangd` (the language server). Both come from the Visual Studio Build Tools installer. #### Install Visual Studio 2026 Build Tools ```powershell winget install -e --id Microsoft.VisualStudio.2026.BuildTools --override "--passive --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended" ``` This single command pulls in: | Component | What it gives you | |-----------|-------------------| | `Microsoft.VisualStudio.Workload.VCTools` | "Desktop development with C++" — MSVC compiler (`cl.exe`), linker (`link.exe`), MSBuild | | `Microsoft.VisualStudio.Component.VC.Llvm.Clang` | LLVM's `clang.exe` and `clang++.exe`, plus `clangd` | | `Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset` | `clang-cl.exe` — Clang with MSVC-compatible flags, drop-in for `cl.exe` | | `Microsoft.VisualStudio.Component.VC.CMake.Project` | CMake + Ninja bundled, plus VS-aware CMake integration | | `Microsoft.VisualStudio.Component.Windows11SDK.26100` | Windows 11 SDK 26100 — system headers, libraries, `cdb` & `WinDbg` debuggers | | `--includeRecommended` | Recommended sub-components for the VCTools workload (latest MSVC, libraries, ATL/MFC if asked) | The download is multi-gigabyte and the install takes 10–25 minutes depending on your disk and connection. Both ARM64 and x64 hosts get the right compilers — the installer detects your architecture and pulls native binaries plus cross-compilers to the other arch. > [!info] If you already installed VS Build Tools for the Rust guide — read this > The Rust guide installs a **subset** of the same Build Tools (MSVC + Windows SDK only — Rust needs the linker, not the whole C toolchain). If you already ran the Rust install, **don't reinstall** — instead, **add the missing components** with the same winget command: > ```powershell > winget install -e --id Microsoft.VisualStudio.2026.BuildTools --override "--passive --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.VC.CMake.Project" > ``` > winget detects the existing Build Tools install and runs the installer in "modify" mode, adding only what's missing. You can also do this through the GUI: Start menu → **Visual Studio Installer** → **Modify** → check the additional components → **Install**. #### Verify the install After the install completes, open the **Start menu** and look for **"Developer PowerShell for VS 2026"**. This is a *specially-initialized* PowerShell where `cl.exe`, `link.exe`, `cmake`, `ninja`, and the Windows SDK headers/libraries are all on the PATH. A normal PowerShell window **will not find any of these tools** until you do one of the two fixes below. Launch a Developer PowerShell once and verify: ```powershell cl # Should print the MSVC banner: "Microsoft (R) C/C++ Optimizing Compiler Version 19.xx..." clang-cl --version # Should print: "clang version 19.x ... Target: aarch64-pc-windows-msvc" (or x86_64) cmake --version ninja --version ``` If `cl` errors with "term 'cl' is not recognized," you're in a regular PowerShell, not a Developer PowerShell. See "Making the MSVC environment available everywhere" below. ### Making the MSVC environment available everywhere The VS installer registers a script (`VsDevCmd.bat`) that sets up the dozens of environment variables MSVC needs (`INCLUDE`, `LIB`, `LIBPATH`, `WindowsSdkDir`, and a long list of others). A **"Developer PowerShell for VS 2026"** Start menu shortcut runs that script and launches PowerShell with the result. A regular `pwsh` tab doesn't. You have two reasonable options: #### Option 1 — Add Developer PowerShell as a Windows Terminal profile Pin the Developer PowerShell as a dedicated Terminal tab. Open Windows Terminal → **Settings** → **+ Add a new profile** → **Duplicate** the existing PowerShell profile, then set: | Setting | Value | |---------|-------| | **Name** | `Developer PowerShell for VS 2026` | | **Command line** | `pwsh.exe -NoExit -Command "&{ Import-Module \"\"\"${env:ProgramFiles}\\Microsoft Visual Studio\\2026\\BuildTools\\Common7\\Tools\\Microsoft.VisualStudio.DevShell.dll\"\"\"; Enter-VsDevShell -VsInstallPath \"\"\"${env:ProgramFiles}\\Microsoft Visual Studio\\2026\\BuildTools\"\"\" -SkipAutomaticLocation -DevCmdArguments '-arch=$($env:PROCESSOR_ARCHITECTURE.ToLower())' }"` | | **Starting directory** | `%USERPROFILE%\projects` | | **Icon** | `ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png` (the PowerShell icon) | Use this profile any time you need to compile C. Your regular PowerShell tabs are unaffected. #### Option 2 — Initialize the MSVC environment in `$PROFILE` (recommended) If you're doing C work daily, having to remember to open a "special" PowerShell gets old. Run the VS environment script once per session, automatically, from your PowerShell profile. Append this near the top of `$PROFILE` (above the Starship init): ```powershell # ── MSVC environment (Visual Studio Build Tools) ────────────── # Make cl.exe, clang-cl, cmake, ninja, and the Windows SDK available # in every PowerShell tab — not just "Developer PowerShell for VS 2026". if (-not $env:VSCMD_VER) { $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" if (Test-Path $vswhere) { $vsPath = & $vswhere -latest -property installationPath if ($vsPath) { $arch = $env:PROCESSOR_ARCHITECTURE.ToLower() & "${env:COMSPEC}" /c "`"$vsPath\Common7\Tools\VsDevCmd.bat`" -arch=$arch -no_logo && set" | ForEach-Object { if ($_ -match '^([^=]+)=(.*)') { Set-Item -Path "env:$($matches[1])" -Value $matches[2] } } } } } ``` What this does: 1. **`if (-not $env:VSCMD_VER)`** — only runs if MSVC isn't already initialized (so opening a nested shell doesn't re-run it) 2. **`vswhere.exe`** — Microsoft's tool for discovering installed Visual Studio products; returns the install path regardless of version or edition 3. **`VsDevCmd.bat -arch=<arch>`** — Microsoft's script that prints all the env vars MSVC needs; `-arch=arm64` on ARM64 hosts, `-arch=amd64` on x64 4. **`-no_logo`** — suppresses the "** Visual Studio 2026 Developer Command Prompt **" banner 5. **`ForEach-Object { Set-Item env:... }`** — parses the `KEY=VALUE` output and applies each setting to the current PowerShell session After this is in your profile, **every** new PowerShell tab transparently has `cl`, `clang-cl`, `cmake`, `ninja`, and `link` on its PATH. There's a ~1 second cost at shell startup (the cost of running `VsDevCmd.bat`), which is acceptable for an everyday-C workflow. > [!tip] Recommended: pick Option 2 > For most users doing any regular C work, Option 2 is the right choice. The startup cost is small, and you don't have to remember which Terminal tab can compile and which can't. If you only touch C occasionally, Option 1 is fine — keep the Developer PowerShell as a dedicated profile and forget about it. ### Build tools Both **CMake** and **Ninja** are bundled with VS Build Tools when the `VC.CMake.Project` component is installed (it's in the winget command above). If you want them on the PATH independent of the MSVC environment (useful for working with C projects that bring their own compiler choice), install them via winget: ```powershell winget install -e --id Kitware.CMake winget install -e --id Ninja-build.Ninja ``` The winget builds drop binaries onto your normal PATH (`C:\Program Files\CMake\bin\`, `C:\Program Files\Ninja\`), so they work in any PowerShell — no Developer environment required. Verify either path: ```powershell cmake --version ninja --version ``` > [!info] Two CMakes coexist fine > If you have both the bundled CMake (from VS Build Tools) and the standalone winget CMake, that's harmless — whichever PATH entry comes first wins. The bundled one is typically slightly behind the latest release; the winget one tracks Kitware's stable channel. They behave identically for everyday work. ### Debugger choice On Windows, the right default is the **`cppvsdbg`** debugger that ships with Microsoft's C/C++ VS Code extension. It drives the Visual Studio debugger backend, understands MSVC's PDB debug-info format natively, supports ARM64 and x64, and "just works" with binaries built by `cl.exe` or `clang-cl.exe`. | Debugger | When to use | How to invoke | |----------|-------------|---------------| | **`cppvsdbg`** | Default — debugging MSVC and clang-cl binaries | Microsoft C/C++ VS Code extension (`ms-vscode.cpptools`) | | **`lldb-dap`** | If you specifically want LLVM's debugger (e.g., for matching Linux/Mac muscle memory) | `llvm-vs-code-extensions.lldb-dap` VS Code extension | | **`WinDbg`** | Heavy lifting — crash dumps, kernel debugging, deep PDB inspection | Windows SDK; included via the Build Tools install | | **`cdb`** | Console-style WinDbg, scriptable | Windows SDK | > [!info] No gdb in this guide > `gdb` is GNU-only and doesn't understand MSVC's PDB debug-info format. Even if you `winget install gdb` and produce a binary with `clang-cl`, gdb won't be able to step through it usefully. Stick with `cppvsdbg` (Visual Studio Debugger) for MSVC/clang-cl binaries, or `lldb-dap` if you specifically want LLVM tooling. gdb is the right tool on Linux; on Windows it's the wrong one. > [!info] No Valgrind on Windows > Valgrind has never been ported to Windows — it depends on Linux-specific syscalls and ptrace internals. The replacement is **AddressSanitizer**, which MSVC supports on both x64 and ARM64 (ARM64 support landed in MSVC 14.51). See [Optional: static analysis and AddressSanitizer](#optional-static-analysis-and-addresssanitizer) below. --- ## VS Code extensions for C Install these from any PowerShell (the Developer environment isn't needed for `code --install-extension`): ```powershell # Microsoft's C/C++ extension (IntelliSense + cppvsdbg debugger) code --install-extension ms-vscode.cpptools code --install-extension ms-vscode.cpptools-extension-pack # CMake integration — configure, build, and run from the command palette code --install-extension ms-vscode.cmake-tools code --install-extension twxs.cmake # clangd — language server alternative to Microsoft's IntelliSense code --install-extension llvm-vs-code-extensions.vscode-clangd # Optional: LLVM's debugger if you prefer it over cppvsdbg code --install-extension llvm-vs-code-extensions.lldb-dap ``` > [!warning] clangd vs Microsoft IntelliSense — pick one > Running both causes dueling diagnostics. Convention: if you install `vscode-clangd`, disable Microsoft's IntelliSense with `"C_Cpp.intelliSenseEngine": "disabled"`. If you prefer Microsoft's, skip the clangd extension. clangd is faster and more standards-correct; Microsoft's is more beginner-friendly and includes the `cppvsdbg` debugger we use below. **You still need `ms-vscode.cpptools` even if you disable its IntelliSense engine** — that extension is what provides the debugger. > [!info] cpptools auto-detects MSVC > The Microsoft C/C++ extension scans your machine for installed compilers on first run. It will find your VS Build Tools install and offer to use MSVC as the default IntelliSense backend. If you accept, you don't need clangd at all — but you give up some of clangd's faster, more standards-strict diagnostics. --- ## VS Code settings Append to your existing `settings.json` (`%APPDATA%\Code\User\settings.json`): ```jsonc { // ── C/C++ ───────────────────────────────────────────────── "[c]": { "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", "editor.tabSize": 4, "editor.insertSpaces": true, "editor.formatOnSave": true }, "[cpp]": { "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", "editor.tabSize": 4, "editor.formatOnSave": true }, // Disable Microsoft IntelliSense if using clangd (pick one) "C_Cpp.intelliSenseEngine": "disabled", // clangd settings "clangd.arguments": [ "--background-index", "--clang-tidy", "--header-insertion=iwyu", "--completion-style=detailed" ], // CMake Tools "cmake.configureOnOpen": true, "cmake.buildDirectory": "${workspaceFolder}/build", "cmake.generator": "Ninja", // On Windows, tell CMake Tools to prefer the MSVC toolchain. // The "Visual Studio Build Tools 2026 Release - amd64" kit is auto-detected // after the first time you run "CMake: Scan for Kits" from the command palette. "cmake.preferredGenerators": ["Ninja"] } ``` > [!info] First-time kit scan > Open the command palette (`Ctrl+Shift+P`) and run **CMake: Scan for Kits**. The CMake Tools extension finds your VS Build Tools install and offers a list of toolchain "kits" (one per architecture: `amd64`, `arm64`, `x86`, and the cross-compilers). Pick `Visual Studio Build Tools 2026 Release - <arch>` matching your host. You only need to do this once per machine. --- ## Full demo: A calculator program This walks through building, running, debugging, and publishing a simple four-function calculator from scratch. ### Create the project ```powershell cd $HOME\projects mkdir calc-c; cd calc-c mkdir src, tests ``` ### Project layout ``` calc-c\ ├── CMakeLists.txt ├── .clang-format ├── .gitignore ├── src\ │ ├── calc_lib.h │ ├── calc_lib.c │ └── main.c └── tests\ └── test_calc.c ``` (Unlike the Linux/Mac guides, we don't `curl` Unity's source into `tests\` — CMake's `FetchContent` will download it during configure. More on that below.) ### `src\calc_lib.h` — the public API ```c #ifndef CALC_LIB_H #define CALC_LIB_H /* Result type: carries either a value or an error */ typedef enum { CALC_OK = 0, CALC_ERR_DIVIDE_BY_ZERO, CALC_ERR_UNKNOWN_OP, } calc_status_t; typedef struct { calc_status_t status; double value; } calc_result_t; calc_result_t calc_add(double a, double b); calc_result_t calc_sub(double a, double b); calc_result_t calc_mul(double a, double b); calc_result_t calc_div(double a, double b); /* Dispatch by operator string: "+", "-", "*", "x", "/" */ calc_result_t calc_calculate(double a, const char *op, double b); #endif /* CALC_LIB_H */ ``` ### `src\calc_lib.c` — implementation ```c #include "calc_lib.h" #include <string.h> calc_result_t calc_add(double a, double b) { return (calc_result_t){CALC_OK, a + b}; } calc_result_t calc_sub(double a, double b) { return (calc_result_t){CALC_OK, a - b}; } calc_result_t calc_mul(double a, double b) { return (calc_result_t){CALC_OK, a * b}; } calc_result_t calc_div(double a, double b) { if (b == 0.0) { return (calc_result_t){CALC_ERR_DIVIDE_BY_ZERO, 0.0}; } return (calc_result_t){CALC_OK, a / b}; } calc_result_t calc_calculate(double a, const char *op, double b) { if (strcmp(op, "+") == 0) return calc_add(a, b); if (strcmp(op, "-") == 0) return calc_sub(a, b); if (strcmp(op, "*") == 0 || strcmp(op, "x") == 0) return calc_mul(a, b); if (strcmp(op, "/") == 0) return calc_div(a, b); return (calc_result_t){CALC_ERR_UNKNOWN_OP, 0.0}; } ``` ### `src\main.c` — the CLI ```c #include <stdio.h> #include <stdlib.h> #include "calc_lib.h" static void print_usage(const char *prog) { fprintf(stderr, "Usage: %s <number> <op> <number>\n", prog); fprintf(stderr, " op: + - * /\n"); fprintf(stderr, "Example: %s 6 + 7\n", prog); } int main(int argc, char *argv[]) { if (argc != 4) { print_usage(argv[0]); return 1; } double a = strtod(argv[1], NULL); double b = strtod(argv[3], NULL); calc_result_t result = calc_calculate(a, argv[2], b); switch (result.status) { case CALC_OK: printf("%g\n", result.value); return 0; case CALC_ERR_DIVIDE_BY_ZERO: fprintf(stderr, "Error: division by zero\n"); return 1; case CALC_ERR_UNKNOWN_OP: fprintf(stderr, "Error: unknown operator '%s'\n", argv[2]); print_usage(argv[0]); return 1; } return 1; } ``` ### Add Unity for unit testing — via CMake FetchContent [Unity](https://github.com/ThrowTheSwitch/Unity) is a minimal C test framework. On Linux and Mac the guides `curl` three files into `tests/` and check them into git; on Windows we instead let CMake fetch Unity during the configure step. This is cleaner — no vendored third-party source in your repo, and the version is pinned by tag. The `FetchContent` block goes in `CMakeLists.txt` (next step), so there's nothing to install yet. ### `tests\test_calc.c` — unit tests ```c #include "unity.h" #include "calc_lib.h" void setUp(void) {} void tearDown(void) {} void test_add(void) { calc_result_t r = calc_calculate(6, "+", 7); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(13.0, r.value); } void test_sub(void) { calc_result_t r = calc_calculate(10, "-", 3); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(7.0, r.value); } void test_mul_star(void) { calc_result_t r = calc_calculate(4, "*", 5); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(20.0, r.value); } void test_mul_x(void) { calc_result_t r = calc_calculate(4, "x", 5); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(20.0, r.value); } void test_div(void) { calc_result_t r = calc_calculate(20, "/", 4); TEST_ASSERT_EQUAL_INT(CALC_OK, r.status); TEST_ASSERT_EQUAL_DOUBLE(5.0, r.value); } void test_div_by_zero(void) { calc_result_t r = calc_calculate(5, "/", 0); TEST_ASSERT_EQUAL_INT(CALC_ERR_DIVIDE_BY_ZERO, r.status); } void test_unknown_operator(void) { calc_result_t r = calc_calculate(1, "?", 2); TEST_ASSERT_EQUAL_INT(CALC_ERR_UNKNOWN_OP, r.status); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_add); RUN_TEST(test_sub); RUN_TEST(test_mul_star); RUN_TEST(test_mul_x); RUN_TEST(test_div); RUN_TEST(test_div_by_zero); RUN_TEST(test_unknown_operator); return UNITY_END(); } ``` ### `CMakeLists.txt` — the build configuration ```cmake cmake_minimum_required(VERSION 3.20) project(calc C) set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # ── MSVC-specific compiler flags ───────────────────────────── # On other compilers (clang-cl, clang, gcc) we'd use -Wall -Wextra etc. # MSVC uses /W4 (its highest standard warning level). if(MSVC) add_compile_options(/W4 /permissive- /Zc:preprocessor) else() add_compile_options(-Wall -Wextra -Wpedantic) endif() # The calculator library (reusable by both CLI and tests) add_library(calc_lib STATIC src/calc_lib.c) target_include_directories(calc_lib PUBLIC src) # The CLI executable add_executable(calc src/main.c) target_link_libraries(calc PRIVATE calc_lib) # ── Tests via Unity, pulled in by FetchContent ─────────────── include(FetchContent) FetchContent_Declare( unity GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git GIT_TAG v2.6.0 ) FetchContent_MakeAvailable(unity) enable_testing() add_executable(test_calc tests/test_calc.c) target_link_libraries(test_calc PRIVATE calc_lib unity) add_test(NAME unit_tests COMMAND test_calc) ``` Notes on the MSVC-specific bits: - **`/W4`** — MSVC's "level 4" warnings, the rough equivalent of gcc/clang's `-Wall -Wextra`. (MSVC also has `/Wall`, but that turns on a long list of pedantic warnings even in system headers; `/W4` is the conventional max in real projects.) - **`/permissive-`** — turns off MSVC's legacy non-conformant behavior. With this on, MSVC enforces the C standard properly (no implicit int, two-phase name lookup, etc.). - **`/Zc:preprocessor`** — opts into MSVC's modern, standards-conformant preprocessor (default off for backwards compatibility, but you want it). The `CMAKE_EXPORT_COMPILE_COMMANDS ON` line is important — it generates `build\compile_commands.json`, which `clangd` reads to understand include paths and flags. Without it, clangd shows spurious errors. > [!info] Why FetchContent instead of vendoring Unity? > On Linux and Mac the guides `curl` three Unity files into `tests/` and commit them. That works, but bloats the repo with third-party source and pins to whatever was current the day you downloaded it. `FetchContent` keeps your tree clean and pins the version explicitly via `GIT_TAG v2.6.0`. The first `cmake -B build -G Ninja` clones Unity into `build/_deps/`, subsequent builds reuse it. The Linux/Mac guides could use this approach too — we're just making it the default on Windows. ### `.clang-format` — code style (optional but recommended) ```yaml BasedOnStyle: LLVM IndentWidth: 4 ColumnLimit: 100 AllowShortFunctionsOnASingleLine: Inline ``` `clang-format.exe` ships with the Clang components of VS Build Tools — it's on PATH inside any Developer PowerShell (or any PowerShell, if you used `$PROFILE` option 2 above). ### `.gitignore` ``` # Build artifacts build/ out/ # MSVC debug + link intermediates *.pdb *.ilk *.exp *.obj # MSVC static libraries (uncomment if your repo never ships .lib files) # *.lib # Visual Studio / VS Code per-user state .vs/ *.user *.suo # CMake FetchContent cache _deps/ ``` A few of these need explaining: - **`*.pdb`** — MSVC debug symbol files, written next to every `.exe` and `.dll`. Large, regenerated on every build, never useful in git. - **`*.ilk`** — MSVC incremental linker state. Same deal. - **`*.exp`** — MSVC export files generated when building DLLs. - **`*.obj`** — MSVC object files (the equivalent of `.o` on Linux/Mac). - **`*.lib`** — MSVC static libraries. Usually generated; only commit if you have a vendored binary dep. - **`.vs/`** — Visual Studio's per-user IDE state (cached IntelliSense, browse databases). Always per-user, never shared. ### Build and run From a PowerShell with the MSVC environment available (a Developer PowerShell, or any PowerShell if you wired the env into `$PROFILE`): ```powershell # Configure (generates build\ with Ninja build files using MSVC) cmake -B build -G Ninja # Build cmake --build build # Run the CLI .\build\calc.exe 6 + 7 .\build\calc.exe 10 - 3 .\build\calc.exe 4 x 5 .\build\calc.exe 20 / 4 # Run unit tests .\build\test_calc.exe # Or via CTest: cd build; ctest --output-on-failure; cd .. ``` > [!info] `.exe` is explicit on Windows > PowerShell will run `.\build\calc` (without the extension) because of `$env:PATHEXT`, but for cross-platform clarity and to avoid PowerShell trying to interpret it as a parameter, prefer the explicit `.exe` in scripts. The Linux/Mac equivalent is `./build/calc` with no extension. ### Debug in VS Code Create `.vscode\launch.json`. On Windows the right default is the `cppvsdbg` debugger from the Microsoft C/C++ extension: ```jsonc { "version": "0.2.0", "configurations": [ { "name": "Debug calc", "type": "cppvsdbg", "request": "launch", "program": "${workspaceFolder}\\build\\calc.exe", "args": ["6", "+", "7"], "cwd": "${workspaceFolder}", "stopAtEntry": false, "console": "integratedTerminal" } ] } ``` Set a breakpoint on the `calc_add` function, hit **F5**, and you'll step through native code with full variable inspection. PDB debug info generated by MSVC is rich — function names, local variables, even optimized-away inlines (with `/Zi`) all show up. > [!info] Prefer LLDB? > If you installed the `lldb-dap` adapter (`code --install-extension llvm-vs-code-extensions.lldb-dap`), build the project with `clang-cl` (set `CC=clang-cl` before the configure step) and change `"type"` to `"lldb-dap"`. `cppvsdbg` is the lower-friction default — `lldb-dap` only outperforms it if you specifically want LLVM's debugger UX. > [!info] No `MIMode: gdb` lines > The Linux guide's `launch.json` includes `"MIMode": "gdb"` and a `setupCommands` block — both are gdb-specific. `cppvsdbg` has its own native protocol and doesn't need them. ### Publish to GitHub ```powershell git init git add . git commit -m "Initial commit: four-function calculator" gh repo create calc-c --private --source=. --remote=origin --push ``` ### `CLAUDE.md` for this project ```markdown # Project: calc-c ## Purpose Four-function command-line calculator in C — pedagogical example. ## Conventions - C17 standard, compiled with MSVC (cl.exe) by default; clang-cl also supported - Library (src/calc_lib.{c,h}) + CLI (src/main.c) + tests (tests/test_calc.c) - Build: CMake + Ninja (build dir: ./build/) - Format: clang-format (LLVM base, 4-space indent, 100-col limit) - Warnings: /W4 /permissive- on MSVC; -Wall -Wextra -Wpedantic on Clang - Tests with Unity (fetched by CMake FetchContent, pinned to v2.6.0) ## Commands - Configure: `cmake -B build -G Ninja` - Build: `cmake --build build` - Run CLI: `.\build\calc.exe <a> <op> <b>` - Run tests: `.\build\test_calc.exe` or `cd build; ctest --output-on-failure` - Format: `clang-format -i src/*.c src/*.h tests/*.c` - Sanitizer build: `cmake -B build-asan -G Ninja -DENABLE_ASAN=ON` - Clean: `Remove-Item -Recurse -Force build, build-asan -ErrorAction SilentlyContinue` ## Environment - Requires a PowerShell with the MSVC environment initialized (either a "Developer PowerShell for VS 2026" tab, or a regular PowerShell whose $PROFILE runs VsDevCmd.bat — see C_Development_Windows_Native_Setup) ## Style - snake_case for functions and variables - SCREAMING_SNAKE_CASE for macros and enum values - Static linkage for internal helpers - Return calc_result_t (status + value) instead of errno-style out-params ``` --- ## Full demo: A calculator GUI Same calculator, wrapped in a graphical interface. This uses **raylib**, a simple, modern, BSD-licensed graphics library with first-class Windows support on both ARM64 and x64. ### Why raylib? | Option | Pros | Cons | |--------|------|------| | **raylib** | Single dependency, CMake-friendly, modern, native ARM64+x64, simple immediate-mode API | Custom widget look (not native Win32 controls) | | **Win32 / Common Controls** | True native Windows look | Verbose C, dated API, lots of boilerplate | | **Dear ImGui** | Excellent for tools and debug UIs | Bindings are C++ (cimgui wraps to C) | | **GTK / Qt** | Full cross-platform widget toolkits | Heavy dependency chain on Windows; Qt is C++-first | | **WinUI 3** | Modern Microsoft-blessed UI | C++/WinRT-first; barely usable from C | For a pedagogical calculator in C on Windows, raylib is the right default — it builds from source via CMake (no precompiled-binary hunting), runs natively on both architectures, and the API is designed to be learnable in an afternoon. ### Two ways to get raylib on Windows #### Path A — CMake FetchContent (recommended for this demo) Same approach we used for Unity: let CMake clone raylib during the configure step and build it as part of the project. Zero extra tools to install. The downside is the first configure takes 2–5 minutes while CMake clones the raylib source and builds it; subsequent builds reuse the cached static library. #### Path B — vcpkg (recommended for serious C/C++ projects) [vcpkg](https://vcpkg.io/) is Microsoft's C/C++ package manager — the apt/Homebrew of the Windows C world. Install it once, then `vcpkg install raylib` and any other library you need. CMake integrates via a toolchain file: ```powershell git clone https://github.com/microsoft/vcpkg $HOME\vcpkg & $HOME\vcpkg\bootstrap-vcpkg.bat & $HOME\vcpkg\vcpkg.exe install raylib ``` Then configure CMake with: ```powershell cmake -B build -G Ninja "-DCMAKE_TOOLCHAIN_FILE=$HOME\vcpkg\scripts\buildsystems\vcpkg.cmake" ``` vcpkg is the right answer for serious C/C++ projects that pull in 5+ dependencies. For a single-library pedagogical demo, FetchContent is simpler. This guide uses **Path A (FetchContent)** to match the Linux/Mac guides' "one library install" approach. If you go with vcpkg, the CMake snippets below need adjustment (`find_package(raylib CONFIG REQUIRED)` instead of FetchContent). ### `src\calc_gui.c` — the GUI ```c #include <stdio.h> #include <string.h> #include <stdlib.h> #include "raylib.h" #include "calc_lib.h" #define SCREEN_WIDTH 300 #define SCREEN_HEIGHT 400 #define BUTTON_WIDTH 65 #define BUTTON_HEIGHT 60 #define PADDING 10 #define DISPLAY_HEIGHT 70 typedef struct { const char *label; int row; int col; } button_t; static const button_t BUTTONS[] = { {"7", 0, 0}, {"8", 0, 1}, {"9", 0, 2}, {"/", 0, 3}, {"4", 1, 0}, {"5", 1, 1}, {"6", 1, 2}, {"*", 1, 3}, {"1", 2, 0}, {"2", 2, 1}, {"3", 2, 2}, {"-", 2, 3}, {"0", 3, 0}, {".", 3, 1}, {"=", 3, 2}, {"+", 3, 3}, }; static const int NUM_BUTTONS = sizeof(BUTTONS) / sizeof(BUTTONS[0]); typedef struct { char display[64]; char current[32]; double stored; char pending_op[4]; int has_stored; } calc_state_t; static void state_reset(calc_state_t *s) { s->current[0] = '\0'; s->stored = 0.0; s->pending_op[0] = '\0'; s->has_stored = 0; strcpy(s->display, "0"); } static void state_append(calc_state_t *s, const char *ch) { size_t len = strlen(s->current); if (len + strlen(ch) < sizeof(s->current) - 1) { strcat(s->current, ch); strcpy(s->display, s->current); } } static void state_apply_pending(calc_state_t *s) { if (s->current[0] == '\0') return; double value = strtod(s->current, NULL); if (!s->has_stored || s->pending_op[0] == '\0') { s->stored = value; s->has_stored = 1; } else { calc_result_t r = calc_calculate(s->stored, s->pending_op, value); if (r.status != CALC_OK) { strcpy(s->display, "Error"); state_reset(s); strcpy(s->display, "Error"); return; } s->stored = r.value; } /* Format: integer if whole, otherwise %g */ if (s->stored == (long long)s->stored) { snprintf(s->display, sizeof(s->display), "%lld", (long long)s->stored); } else { snprintf(s->display, sizeof(s->display), "%g", s->stored); } s->current[0] = '\0'; } static void state_handle_press(calc_state_t *s, const char *label) { if ((label[0] >= '0' && label[0] <= '9') || label[0] == '.') { state_append(s, label); } else if (strcmp(label, "+") == 0 || strcmp(label, "-") == 0 || strcmp(label, "*") == 0 || strcmp(label, "/") == 0) { state_apply_pending(s); strncpy(s->pending_op, label, sizeof(s->pending_op) - 1); } else if (strcmp(label, "=") == 0) { state_apply_pending(s); s->pending_op[0] = '\0'; } } int main(void) { InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Calculator"); SetTargetFPS(60); calc_state_t state; state_reset(&state); const int grid_start_x = PADDING; const int grid_start_y = PADDING + DISPLAY_HEIGHT + PADDING; while (!WindowShouldClose()) { /* Keyboard input */ int key = GetCharPressed(); while (key > 0) { char ch[2] = {(char)key, '\0'}; if ((key >= '0' && key <= '9') || key == '.' || key == '+' || key == '-' || key == '*' || key == '/' || key == '=') { state_handle_press(&state, ch); } key = GetCharPressed(); } if (IsKeyPressed(KEY_ENTER)) state_handle_press(&state, "="); if (IsKeyPressed(KEY_ESCAPE)) state_reset(&state); if (IsKeyPressed(KEY_BACKSPACE)) { size_t len = strlen(state.current); if (len > 0) { state.current[len - 1] = '\0'; if (state.current[0] == '\0') { strcpy(state.display, "0"); } else { strcpy(state.display, state.current); } } } /* Draw */ BeginDrawing(); ClearBackground((Color){30, 30, 35, 255}); /* Display */ Rectangle display_rect = {PADDING, PADDING, SCREEN_WIDTH - 2 * PADDING, DISPLAY_HEIGHT}; DrawRectangleRec(display_rect, (Color){50, 50, 55, 255}); DrawRectangleLinesEx(display_rect, 1, GRAY); int display_width = MeasureText(state.display, 28); DrawText(state.display, display_rect.x + display_rect.width - display_width - 10, display_rect.y + 20, 28, RAYWHITE); /* Buttons */ Vector2 mouse = GetMousePosition(); int mouse_clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); for (int i = 0; i < NUM_BUTTONS; i++) { button_t btn = BUTTONS[i]; Rectangle r = { grid_start_x + btn.col * (BUTTON_WIDTH + 5), grid_start_y + btn.row * (BUTTON_HEIGHT + 5), BUTTON_WIDTH, BUTTON_HEIGHT }; int hovered = CheckCollisionPointRec(mouse, r); int is_op = (strchr("+-*/=", btn.label[0]) != NULL); Color fill = is_op ? (Color){200, 120, 40, 255} : (Color){70, 70, 75, 255}; if (hovered) fill = (Color){fill.r + 30, fill.g + 30, fill.b + 30, 255}; DrawRectangleRec(r, fill); int tw = MeasureText(btn.label, 20); DrawText(btn.label, r.x + (r.width - tw) / 2, r.y + 20, 20, RAYWHITE); if (hovered && mouse_clicked) { state_handle_press(&state, btn.label); } } EndDrawing(); } CloseWindow(); return 0; } ``` ### Update `CMakeLists.txt` to build the GUI Append to `CMakeLists.txt`: ```cmake # ── GUI executable (raylib pulled in by FetchContent) ───────── include(FetchContent) FetchContent_Declare( raylib GIT_REPOSITORY https://github.com/raysan5/raylib.git GIT_TAG 5.5 ) # raylib's CMakeLists has options we want to set before MakeAvailable: set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build raylib's example apps set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) # static lib (simpler distribution) FetchContent_MakeAvailable(raylib) add_executable(calc_gui WIN32 src/calc_gui.c) target_link_libraries(calc_gui PRIVATE calc_lib raylib) # Override MSVC's "WIN32 = no console" so we get printf debug output during dev. # Remove the next two lines for a "real" GUI app with no console window. if(MSVC) set_target_properties(calc_gui PROPERTIES LINK_FLAGS "/SUBSYSTEM:CONSOLE" ) endif() ``` A few Windows-specific notes: - **`add_executable(calc_gui WIN32 ...)`** — the `WIN32` keyword tells CMake to build this as a "Windows" (GUI) executable, not a "Console" one. Without it, every launch of `calc_gui.exe` opens a black console window alongside the GUI. - **`/SUBSYSTEM:CONSOLE`** override — for development, we *want* the console window so `printf`/`fprintf(stderr,...)` output is visible. Comment out the `set_target_properties` block for a release build with no console. - **`BUILD_SHARED_LIBS OFF`** — produces a static `raylib.lib` baked into `calc_gui.exe`. The alternative (`ON`) produces `raylib.dll`, which then has to ship alongside your executable. Static is simpler for a single-binary demo. ### Run the GUI ```powershell # First configure: takes 2-5 min to clone and build raylib cmake -B build -G Ninja cmake --build build # Run .\build\calc_gui.exe ``` A 300×400 window opens with the calculator UI. Click buttons or use the keyboard — digits, `+`, `-`, `*`, `/`, `Enter` (for `=`), `Esc` (to clear), and `Backspace` (to delete the last digit). > [!info] raylib supports both architectures > raylib upstream is fully ARM64-clean — `5.5` builds and runs identically on a Surface Pro 11 (Snapdragon X) and an Intel/AMD laptop. The CMake configuration above auto-selects the right backend (Win32 windowing + OpenGL via wgl) on both. ### Tests for the GUI state machine Since the state machine (`state_reset`, `state_handle_press`, `state_apply_pending`) is separate from raylib's drawing, it can be unit-tested. Extract the state struct and helpers from `src\calc_gui.c` into `src\calc_gui_state.{c,h}`, then add tests: ```c /* tests/test_calc_gui_state.c */ #include "unity.h" #include "calc_gui_state.h" void setUp(void) {} void tearDown(void) {} void test_state_initial(void) { calc_state_t s; state_reset(&s); TEST_ASSERT_EQUAL_STRING("0", s.display); } void test_state_simple_addition(void) { calc_state_t s; state_reset(&s); state_handle_press(&s, "6"); state_handle_press(&s, "+"); state_handle_press(&s, "7"); state_handle_press(&s, "="); TEST_ASSERT_EQUAL_STRING("13", s.display); } void test_state_division_by_zero(void) { calc_state_t s; state_reset(&s); state_handle_press(&s, "5"); state_handle_press(&s, "/"); state_handle_press(&s, "0"); state_handle_press(&s, "="); TEST_ASSERT_EQUAL_STRING("Error", s.display); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_state_initial); RUN_TEST(test_state_simple_addition); RUN_TEST(test_state_division_by_zero); return UNITY_END(); } ``` Register the new test executable in `CMakeLists.txt`: ```cmake add_executable(test_calc_gui_state tests/test_calc_gui_state.c) target_link_libraries(test_calc_gui_state PRIVATE calc_lib calc_gui_state unity) add_test(NAME gui_state_tests COMMAND test_calc_gui_state) ``` The raylib drawing in `calc_gui.c` stays out of the test path entirely — pure logic gets pure tests. --- ## Starship prompt — C auto-detection Starship's built-in `[c]` module shows the compiler and standard when a `.c` file or `CMakeLists.txt` exists in the directory. No config needed (the general guide's `starship.toml` already enables it). When you `cd` into `calc-c\`, the prompt shows something like: ``` ~\projects\calc-c main v19.40-cl ``` The version string after the symbol comes from running whichever C compiler Starship finds first on PATH — typically `cl.exe` after you wire in the MSVC environment. --- ## Optional: static analysis and AddressSanitizer The Windows C toolkit covers the same ground as Linux's sanitizers, minus Valgrind (which has never been ported and isn't coming): - **`clang-tidy.exe`** — runs automatically when enabled via clangd's `--clang-tidy` flag (already set in the VS Code settings above). Bundled with the Clang components of VS Build Tools. - **AddressSanitizer** — built into MSVC since VS 16.9 (x64) and MSVC 14.51 (ARM64). Catches buffer overflows, use-after-free, double-free, stack overflows. - **UndefinedBehaviorSanitizer** — supported by `clang-cl`, not by `cl`. Use the clang-cl path below. - **Static Analyzer** — MSVC's built-in `/analyze` does Clang-Tidy-style static analysis at compile time. Output is verbose but useful for one-shot audits. ### Enable AddressSanitizer in `CMakeLists.txt` Add an opt-in flag and a guarded block: ```cmake option(ENABLE_ASAN "Build with AddressSanitizer" OFF) if(ENABLE_ASAN) if(MSVC) add_compile_options(/fsanitize=address /Zi) # ASan on MSVC needs /INCREMENTAL:NO at link time add_link_options(/INCREMENTAL:NO) else() add_compile_options(-fsanitize=address -g -O1 -fno-omit-frame-pointer) add_link_options(-fsanitize=address) endif() endif() ``` Use a separate build directory so you don't toggle the flag on and off in the same tree: ```powershell cmake -B build-asan -G Ninja -DENABLE_ASAN=ON cmake --build build-asan .\build-asan\calc.exe 6 + 7 .\build-asan\test_calc.exe ``` When ASan catches a problem, it prints a detailed report (with addresses, allocation/free sites, and a Windows-formatted stack trace) and the program exits non-zero. The PDB files generated by `/Zi` give you symbol names in the trace. > [!info] ASan on Windows requires a DLL at runtime > MSVC's AddressSanitizer is implemented as `clang_rt.asan_dynamic-<arch>.dll`, which gets baked into the ASan-built binary's import table. The DLL lives under your Build Tools install (`...\VC\Tools\MSVC\<ver>\bin\Hostx64\x64\` and equivalent). The MSVC env script (`VsDevCmd.bat`) puts this on the PATH; if you launch an ASan-instrumented exe from File Explorer (outside a Dev shell), it'll fail to find the DLL. Either run from a Dev shell or copy the DLL next to your exe. ### MSVC `/analyze` for one-shot static analysis ```powershell cmake -B build-analyze -G Ninja -DCMAKE_C_FLAGS="/analyze" cmake --build build-analyze ``` You'll get a wall of warnings (some genuine, many noisy). Treat it as an audit pass, not a default-on flag. --- ## direnv — auto-activate this project's environment direnv can put built executables on `PATH` when you `cd` in, so you can run `calc` instead of `build\calc.exe` (see [[General_Development_Windows_Native_Setup]] for the PowerShell hook). ```bash # from the project root cat > .envrc <<'EOF' PATH_add build EOF direnv allow ``` > [!warning] `.envrc` is bash, and it does not set up MSVC > direnv runs `.envrc` through bash and applies the result to PowerShell, so write bash syntax (forward slashes are fine) and keep Git for Windows on `PATH`. Don't try to set the MSVC toolchain here — the compiler environment comes from the Developer PowerShell / `VsDevCmd.bat` (see "Making the MSVC environment available everywhere" above). Keep `.envrc` to `PATH` and plain env vars. --- ## Troubleshooting > [!warning] `cl.exe` not recognized in this PowerShell > You're in a regular PowerShell, not a Developer PowerShell. Either launch a "Developer PowerShell for VS 2026" tab from the Start menu (or your Windows Terminal profile), or add the `VsDevCmd.bat`-running block to your `$PROFILE` (see "Making the MSVC environment available everywhere" above). Run `$env:VSCMD_VER` — it should print a version like `2.0.0`; if it's empty, the environment isn't initialized. > [!warning] clangd shows "file not found" for standard headers > Build the project at least once so `compile_commands.json` is generated. Ensure `CMAKE_EXPORT_COMPILE_COMMANDS ON` is in your CMakeLists.txt. Restart VS Code after the first build. On Windows specifically, `compile_commands.json` will contain MSVC-style flags (`/W4`, `/permissive-`, etc.); clangd handles them, but if you see flag-related noise, set `clangd.arguments` to include `--query-driver=**/cl.exe,**/clang-cl.exe` so clangd asks the compiler for its system include paths. > [!warning] `cmake -B build -G Ninja` says "could not find compiler" > CMake's initial probe couldn't find a C compiler. The most common cause: your PowerShell doesn't have the MSVC environment loaded. Open a Developer PowerShell (or load the env per `$PROFILE`), then try again. If that's not it, run `cmake --debug-find -B build -G Ninja` and look for the compiler-discovery step. > [!warning] FetchContent download fails behind a corporate proxy > Set `HTTPS_PROXY` and `HTTP_PROXY` before invoking CMake: > ```powershell > $env:HTTPS_PROXY = "http://proxy.example.com:8080" > $env:HTTP_PROXY = "http://proxy.example.com:8080" > ``` > Or skip FetchContent for raylib and Unity entirely, install via vcpkg instead (vcpkg has the same proxy issue, but if your corporate setup proxies the Microsoft CDN already, vcpkg works). > [!warning] `calc_gui.exe` opens a console window alongside the GUI > You either forgot the `WIN32` keyword on `add_executable`, or you left the `/SUBSYSTEM:CONSOLE` override in place. For development, the console is useful (lets you see `printf` debug output); for a polished build, remove the override and the linker uses `/SUBSYSTEM:WINDOWS` automatically due to the `WIN32` keyword. > [!warning] ASan-instrumented binary errors with "clang_rt.asan_dynamic-...dll was not found" > The ASan runtime DLL lives under your VS install but isn't on the system PATH. Launch from a Developer PowerShell (the env script puts it on PATH), or copy `clang_rt.asan_dynamic-x86_64.dll` (or `-aarch64`) from `C:\Program Files\Microsoft Visual Studio\2026\BuildTools\VC\Tools\MSVC\<ver>\bin\Host<arch>\<arch>\` into the same directory as your exe. > [!warning] `Get-Command claude` works in a regular PowerShell but my Dev shell can't find it > The "Developer PowerShell for VS 2026" Start menu shortcut launches PowerShell with `-NoProfile`, skipping your `$PROFILE` (and therefore the PATH additions/aliases you set up). Either edit the shortcut to remove `-NoProfile`, or use the Windows Terminal profile from Option 1 above (it doesn't pass `-NoProfile`). > [!warning] Two CMakes on PATH — which one is picked? > Run `Get-Command cmake -All` to see all candidates and their order. The first wins. If you want the winget-installed CMake to take precedence over the bundled-with-VS one, ensure `C:\Program Files\CMake\bin` appears before any VS-internal path in `$env:PATH`. The bundled CMake is usually slightly behind the latest release; both work for everything in this guide. > [!warning] Tests crash with "The application failed to initialize properly (0xc0000142)" on ARM64 > You probably built with the x64 cross-compiler instead of the native ARM64 one. Confirm `cl` and `link` are the ARM64 versions: `(Get-Command cl).Source` should contain `\arm64\` (not `\x64\`). The `VsDevCmd.bat -arch=arm64` invocation in the `$PROFILE` snippet selects the right toolset; if you're in a Developer PowerShell, make sure it's the ARM64 one (or run `Enter-VsDevShell -DevCmdArguments '-arch=arm64'` to switch). --- ## Summary — the one-shot C addition > [!warning] This is a checklist, not a script > The block below is **not a script you can save and run.** It's a numbered sequence of commands to **copy and paste into PowerShell one block at a time**, in order. Two steps need your attention: > > - **Step 3** edits your `$PROFILE` so the MSVC environment loads in every PowerShell tab. Do this once. > - **Step 5** appends the C/C++ block to your VS Code `settings.json`. Without that, clangd and CMake Tools won't be configured. ```powershell # 1. Install Visual Studio 2026 Build Tools (MSVC, Clang, CMake, Ninja, Windows SDK) # Multi-gigabyte download; ~15-25 minutes. Includes ARM64 + x64 native toolchains. winget install -e --id Microsoft.VisualStudio.2026.BuildTools --override "--passive --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended" # 2. (Optional) Standalone CMake + Ninja on PATH outside the MSVC env winget install -e --id Kitware.CMake winget install -e --id Ninja-build.Ninja # 3. Add the MSVC environment auto-init to your $PROFILE # (Paste the if-block from "Making the MSVC environment available everywhere" # into $PROFILE near the top, above the Starship init.) code $PROFILE # 4. VS Code extensions code --install-extension ms-vscode.cpptools code --install-extension ms-vscode.cpptools-extension-pack code --install-extension ms-vscode.cmake-tools code --install-extension twxs.cmake code --install-extension llvm-vs-code-extensions.vscode-clangd # Optional, if you prefer LLVM's debugger: code --install-extension llvm-vs-code-extensions.lldb-dap # 5. Append the C/C++ settings block (from "VS Code settings" above) # to %APPDATA%\Code\User\settings.json # 6. Open a fresh PowerShell tab and verify cl # MSVC banner clang-cl --version # Clang version cmake --version ninja --version ``` After the upfront install (10–25 minutes), day-to-day C work on Windows mirrors the Linux and Mac guides almost exactly — `cmake -B build -G Ninja`, `cmake --build build`, run the exe, repeat. --- ## Related notes **The other Windows native language guides (share the VS Build Tools install):** - [[General_Development_Windows_Native_Setup]] — the prerequisite for this guide - [[Python_Development_Windows_Native_Setup]] - [[Ruby_Development_Windows_Native_Setup]] - [[Go_Development_Windows_Native_Setup]] - [[Rust_Development_Windows_Native_Setup]] — installs a subset of the same VS Build Tools > [!info] Shared install with the Rust guide > The Rust guide installs the **same** Visual Studio Build Tools, but only adds `Microsoft.VisualStudio.Workload.VCTools` (MSVC + Windows SDK) — it doesn't pull in the Clang components, CMake, or Ninja. If you've already done the Rust setup, run the modify-install command from the "If you already installed VS Build Tools for the Rust guide" callout above instead of the full install. **Same setup on other platforms:** - [[C_Development_Ubuntu_Setup]] — the Linux counterpart (gcc + gdb + Valgrind) - [[C_Development_Mac_Tahoe_Setup]] — the macOS counterpart (Apple Clang + lldb) **The other Windows path:** - [[WSL2 Windows Development Setup]] — runs the Linux C guide inside Windows; gives you gcc, gdb, and Valgrind at the cost of running through the WSL layer **Topic references:** - [Microsoft Learn — Use the Microsoft C++ toolset from the command line](https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line) - [Microsoft Learn — AddressSanitizer for Windows](https://learn.microsoft.com/en-us/cpp/sanitizers/asan) - [CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html) - [vcpkg documentation](https://learn.microsoft.com/en-us/vcpkg/) - [raylib on GitHub](https://github.com/raysan5/raylib) - [ThrowTheSwitch / Unity on GitHub](https://github.com/ThrowTheSwitch/Unity) - [LLVM clang-cl docs](https://clang.llvm.org/docs/UsersManual.html#clang-cl)