Files
skills-public/code-languages/c-cpp.md
T
BluscreamandClaude Opus 5 58525056a1 docs: use <repo>/<rel path> links throughout, document the convention
All cross-skill links now spell out the repository: skills-public/foo.md,
skills-private/bar.md. These resolve from the shared parent directory, so one
spelling works from either repo and from code-languages/ subdirectories —
replacing the previous mix of ./, ../ and absolute file:// URLs. Link labels
normalised to match. Convention added to the README conventions list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:44:23 +02:00

6.5 KiB

name, description
name description
c-cpp 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. 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

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

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 <stdint.h> (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.

  • coding-dev.md — the general rules this extends.
  • rust.md — the preferred language for new native work, and the usual consumer of these FFI boundaries.