Generic coding rules and preferences distilled from existing agent rule files, project AGENTS.md/.rules, and saved memories: - strictness maximal; suppressions must be reported to the user - never filter build/lint/test output through grep/tail/head - reuse > fork > self-implement; generic code becomes its own package - 1000-line file / 100-line function limits, split by concern - regression test every fixed bug; never test against real user data - events over polling; scope discipline; honest reporting Per-language: rust, js-typescript, bash, python, go, csharp-dotnet, c-cpp. Each extends coding-dev.md rather than repeating it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.5 KiB
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
)
-Werrorin the build gate.-Wconversionand-Wshadoware 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-tidywithbugprone-*,cert-*,performance-*,modernize-*.clang-formatwith 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_ptrby default;std::shared_ptronly where ownership is genuinely shared.std::weak_ptrto 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::spanfor 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, andnoexceptwherever 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 bareintfor anything with a defined width on the wire. - Always the bounded string functions (
snprintf,strncpywith explicit termination); neverstrcpy,sprintf,gets. staticon 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-levelinclude_directoriesor globalCMAKE_CXX_FLAGSmutation. PRIVATE/PUBLIC/INTERFACEchosen 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
FetchContentor 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/ELFCLASS32error 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
freefor 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
ctestruns the suite. - Regression test every fixed bug.
- Debug builds keep frame pointers (
-fno-omit-frame-pointer) so stack traces are usable. -geven 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— the general rules this extends.rust.md— the preferred language for new native work, and the usual consumer of these FFI boundaries.