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

154 lines
6.5 KiB
Markdown

---
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 `<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.
---
## 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.