$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for C and C++ Projects

C/C++ has the widest gap between 'compiles' and 'correct.' AI rules for memory safety, modern C++, undefined behavior prevention, and build system conventions.

8 min read·August 1, 2025

In C/C++, 'compiles and works' is lightyears from 'correct and safe'

Smart pointers, RAII, UB prevention, sanitizers, and modern C++ patterns

Why C/C++ Rules Are the Most Critical AI Rules

C and C++ are where AI-generated code is most dangerous. Every other language on this blog has a runtime safety net — garbage collection, bounds checking, null safety. C/C++ has none. A buffer overflow is a security vulnerability. A dangling pointer is a crash — or worse, silent memory corruption. Undefined behavior means the compiler can do literally anything, including 'working' in debug mode and crashing in production.

AI assistants generate C/C++ that compiles and appears to work in testing but contains latent bugs that only manifest in production: use-after-free, double-free, integer overflow, uninitialized memory reads, and buffer overflows. These bugs are invisible without sanitizers and are the root cause of the majority of security vulnerabilities in systems software.

These rules are split into two sections: modern C++ (C++17/20/23) and C. If your project uses C++, use the C++ rules. If you're in a C codebase, skip to the C-specific section. The core safety principles apply to both.

Rule 1: Smart Pointers and RAII (C++)

The rule: 'Never use raw new/delete. Use std::unique_ptr for exclusive ownership, std::shared_ptr only when ownership is genuinely shared (rare). Use std::make_unique and std::make_shared for construction. Use RAII for all resource management: memory, file handles, locks, sockets. Resources are acquired in constructors and released in destructors — never manual cleanup.'

For containers: 'Use std::vector, std::string, std::array, and std::span instead of raw arrays and C-style strings. Use std::string_view for non-owning string references. Use std::optional instead of nullptr/sentinel values. Use std::variant instead of unions with type tags.'

This is the foundational C++ rule. Raw pointers are the root of every memory safety bug. Smart pointers and RAII eliminate use-after-free, double-free, and memory leaks at the language level. AI assistants still generate raw new/delete because their training data includes decades of pre-C++11 code.

  • std::unique_ptr for exclusive ownership — never raw new/delete
  • std::shared_ptr only for genuine shared ownership — not as default
  • RAII: construct → use → destruct — never manual cleanup
  • std::vector over raw arrays — std::string over char*
  • std::optional over nullptr — std::variant over tagged unions
  • std::span for non-owning array views (C++20)
⚠️ Never Raw new/delete

Raw new/delete is the root of every C++ memory safety bug. std::unique_ptr + RAII eliminates use-after-free, double-free, and leaks at the language level. AI still generates raw new from pre-C++11 training data.

Rule 2: Modern C++ Features (C++17/20/23)

The rule: 'This project uses C++23 (or C++20/17 — specify your version). Use structured bindings for multiple returns: auto [key, value] = map.extract(it). Use if constexpr for compile-time branching. Use concepts (C++20) for template constraints instead of SFINAE. Use std::format (C++20) instead of sprintf or iostream formatting. Use ranges (C++20) for algorithm pipelines.'

For error handling: 'Use std::expected<T, E> (C++23) for functions that can fail. Return errors as values — don't throw exceptions across module boundaries. Use exceptions only for truly exceptional situations (out of memory, programmer errors). Define error types as enums or lightweight structs.'

For concurrency: 'Use std::jthread (C++20) for joinable threads with automatic cancellation. Use std::mutex with std::lock_guard or std::scoped_lock — never manual lock/unlock. Use std::atomic for lock-free shared state. Use coroutines (co_await, co_yield) for async operations where supported by your framework.'

Rule 3: C-Specific Safety Patterns

For C projects (not C++): 'Check every return value from functions that can fail — malloc, fopen, read, write. Handle malloc returning NULL before using the pointer. Use sizeof(*ptr) in malloc calls: int *p = malloc(n * sizeof(*p)) — not malloc(n * sizeof(int)). This prevents type mismatch bugs when the pointer type changes.'

For buffer safety: 'Use snprintf instead of sprintf — always. Use strncpy or strlcpy instead of strcpy. Use fgets instead of gets (gets is removed in C11). Pass buffer sizes alongside pointers in all function signatures: void process(char *buf, size_t buf_len). Check array bounds explicitly before access.'

For memory management: 'Set pointers to NULL after free: free(p); p = NULL. This turns use-after-free into a predictable NULL dereference crash instead of silent corruption. Use valgrind or ASan in all testing. Define cleanup functions and use goto cleanup pattern for multi-resource functions.'

  • Check every malloc/fopen/read return — handle failure before use
  • sizeof(*ptr) in malloc — type-safe allocation
  • snprintf over sprintf — strncpy over strcpy — fgets over gets
  • Pass buffer sizes with pointers — check bounds before access
  • NULL after free — valgrind/ASan in all testing
  • goto cleanup pattern for multi-resource error handling
ℹ️ C Safety Pattern

Set pointers to NULL after free. This turns use-after-free into a predictable NULL crash instead of silent corruption. Simple, effective, catches bugs early.

Rule 4: Preventing Undefined Behavior

The rule: 'Treat undefined behavior as a critical bug — not a theoretical concern. The compiler assumes UB never happens and optimizes accordingly, which can remove your security checks or create exploitable behavior. Common UB sources: signed integer overflow, null pointer dereference, out-of-bounds array access, use-after-free, data races, and uninitialized variable reads.'

For prevention: 'Enable compiler warnings: -Wall -Wextra -Wpedantic -Werror. Enable sanitizers in debug/test builds: -fsanitize=address,undefined,thread. Use static analysis (clang-tidy, cppcheck, PVS-Studio) in CI. Use -ftrapv to trap signed integer overflow. Initialize all variables at declaration — never declare without initializing.'

For the AI specifically: 'The AI generates code that relies on UB 'working' on common platforms. It does — until the compiler version changes, optimization level changes, or target platform changes. Rules that prevent UB ensure code is correct by the standard, not by coincidence.'

💡 UB Is Not Theoretical

The compiler assumes undefined behavior never happens and optimizes accordingly — it can remove your security checks. -fsanitize=address,undefined in test builds catches UB before production.

Rule 5: Build System and Tooling

The rule: 'Use CMake for C++ projects (or Meson, Bazel — specify your choice). Use modern CMake: targets instead of global variables, target_link_libraries instead of link_directories, target_compile_features(mylib PUBLIC cxx_std_23) for standard version. Never use add_definitions or include_directories globally — use target-specific properties.'

For dependencies: 'Use CMake's FetchContent or Conan/vcpkg for dependency management. Pin dependency versions. Never vendor dependencies by copying source code unless there's a specific reason (offline builds, patched forks). Document build prerequisites in README.'

For CI: 'Build with both GCC and Clang — they catch different issues. Build with sanitizers enabled (ASan + UBSan at minimum). Run clang-tidy with a .clang-tidy config. Run clang-format with a .clang-format config. All warnings are errors in CI: -Werror.'

Complete C/C++ Rules Template

Consolidated rules for C and C++ projects. Specify your standard version (C11, C++17, C++20, C++23) in the first line.

  • C++: smart pointers only — never raw new/delete — RAII for all resources
  • std::vector over arrays, std::string over char*, std::optional over nullptr
  • C++20/23: concepts, ranges, std::format, std::expected, coroutines
  • C: check every return, sizeof(*ptr), snprintf, NULL after free
  • No undefined behavior: -Wall -Wextra -Werror, sanitizers in test builds
  • Initialize all variables at declaration — never read uninitialized memory
  • CMake modern targets — FetchContent/Conan for deps — pin versions
  • Build with GCC + Clang, ASan + UBSan, clang-tidy, clang-format in CI