--- name: c-cpp description: >- Use this skill when writing, building, or debugging C or C++ — including compiler warning configuration, sanitizers, modern C++ resource management, build systems (CMake/Meson), 32-bit/cross builds, and FFI boundaries exposed to other languages. --- # C / C++ — Rules & Preferences Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds C/C++ specifics. C and C++ are for the cases with no better option: existing codebases, hard real-time paths, and FFI boundaries. **For new standalone work, prefer Rust** — the memory-safety guarantees are worth more than the familiarity. --- ## 1. Warnings — Everything On, Errors ```cmake target_compile_options(mytarget PRIVATE -Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion -Wsign-conversion -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wdouble-promotion -Wformat=2 -Wnull-dereference -Wimplicit-fallthrough ) ``` - **`-Werror` in the build gate.** `-Wconversion` and `-Wshadow` are noisy on first contact and catch real bugs — do not drop them because a legacy file complains; fix the file or scope the suppression to it. - Run **both** GCC and Clang where possible; each diagnoses things the other misses. - `clang-tidy` with `bugprone-*`, `cert-*`, `performance-*`, `modernize-*`. - `clang-format` with a committed `.clang-format`. No hand-formatting. --- ## 2. Sanitizers — Use Them ```bash cmake -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" ``` - **ASan + UBSan on every debug/test build.** They find the bugs that warnings cannot. - TSan for anything threaded (separate build — it is incompatible with ASan). - Valgrind when a sanitizer build is not possible. - **A sanitizer finding is a bug, not noise.** Fix it. --- ## 3. Resource Management (C++) - **RAII for everything.** Every raw `new`/`delete`, `malloc`/`free`, `fopen`/`fclose`, and native handle pair is a leak waiting for an early return or a throw. - `std::unique_ptr` by default; `std::shared_ptr` only where ownership is genuinely shared. `std::weak_ptr` to break cycles. - **No owning raw pointers.** A raw pointer or reference in a signature means "borrowed, non-owning, outlives the call". - `std::string_view` / `std::span` for non-owning views — and never return one that outlives its backing storage. - **The Rule of Zero**: design so no destructor, copy, or move needs writing. If you must write one, write all five. - `constexpr`, `const`, and `noexcept` wherever they hold. `[[nodiscard]]` on anything whose return value must not be dropped. --- ## 4. C Specifics - C17 or later unless the target forbids it. - **Check every allocation and every return value.** In C, unchecked returns are the primary bug source. - Fixed-width types from `` (`uint32_t`), never bare `int` for anything with a defined width on the wire. - Always the bounded string functions (`snprintf`, `strncpy` with explicit termination); never `strcpy`, `sprintf`, `gets`. - `static` on every function and global not exported from the translation unit. - Prefer a struct-and-functions module boundary with an opaque pointer over exposing layout. --- ## 5. Build Systems - **CMake** (3.20+, target-based) or **Meson** for new projects. Never a hand-written Makefile for anything non-trivial. - Target-based CMake only: `target_link_libraries`, `target_include_directories`, `target_compile_options`. **No directory-level `include_directories` or global `CMAKE_CXX_FLAGS` mutation.** - `PRIVATE` / `PUBLIC` / `INTERFACE` chosen deliberately — this is what keeps include leaks out of dependents. - **Register every new source file with the build system** in the same commit that adds it. A source file the build never compiles will sit there looking correct while every edit to it does nothing. - Vendor dependencies with `FetchContent` or submodules; document which and why. --- ## 6. Cross & Multi-Arch Builds - Keep 32-bit and 64-bit outputs in **separate build directories**; never reuse one. - **A component built for one architecture cannot load one built for another.** An `ELFCLASS64` / `ELFCLASS32` error means a loader path is reaching the wrong build — check the library search path, not the code. - When a build must match an already-installed runtime's version, **pin it explicitly** and note the constraint. A silently mismatched pair fails at load time with a message that points nowhere useful. - Record the exact toolchain and container used for a cross build. "It built on my machine" is not reproducible for native code. --- ## 7. FFI Boundaries When exposing C/C++ to another language: - `extern "C"` on the boundary, with a hand-written, stable, documented header. - **Ownership across the boundary must be explicit** — who allocates, who frees, and with which function. Provide a matching `free` for every allocation you hand out. - **Never let an exception cross an `extern "C"` boundary.** Catch everything at the edge and convert to an error code. - Bindings generated by a tool (`bindgen`, etc.) are regenerated, never hand-edited. - On the consuming side, keep all `unsafe`/unmanaged interop in **one thin wrapper module** that exposes a safe API — never scattered through call sites. > [!WARNING] > **Binding crates/packages that reach into a library's struct fields break when that library > makes the struct opaque.** Pin to a binding version that matches the installed library > generation, and note the constraint where someone will read it before bumping. --- ## 8. Testing & Debugging - GoogleTest or Catch2; CTest integration so `ctest` runs the suite. - **Regression test every fixed bug.** - Debug builds keep frame pointers (`-fno-omit-frame-pointer`) so stack traces are usable. - `-g` even in release builds; ship separate debug symbols rather than none. - **Record dead ends in the project's notes**, especially for native debugging — the investigation that ruled something out is expensive and will otherwise be repeated. Note what did *not* work alongside what did. - When a binary search through a packed/embedded blob finds nothing, that is not evidence the content is absent — packed asset formats defeat naive string searches. Re-check without anchoring before concluding. --- ## 9. Related - [`coding-dev.md`](skills-public/coding-dev.md) — the general rules this extends. - [`rust.md`](skills-public/code-languages/rust.md) — the preferred language for new native work, and the usual consumer of these FFI boundaries.