agentsclimarketplace

C coding

Skill nguyenthdat/opencode-manager/registry/skills/c-coding

Project-scoped OpenCode TUI plugin for grouping and managing MCP servers, custom agent skills, and pinned vendor skill registries.

Install
npx -y skills add nguyenthdat/opencode-manager --skill c-coding

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 13 days oldThe repository was created 13 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Comprehensive idiomatic C guidance: 185 prioritized rules across 15 categories, covering C99/C11/C17/C23 (plain C, not C++). Use aggressively when writing, reviewing, refactoring, debugging, or security-auditing any `.c`/`.h` file — manual memory management, pointer arithmetic, buffer sizing, error-code conventions, undefined behavior, and concurrency are exactly the areas where C code silently goes wrong. Preserve the project's declared C standard version and existing conventions; apply C11/C17/C23 features (`_Generic`, `static_assert`, designated initializers, `<stdint.h>`, `nullptr`, `constexpr`, `#embed`) only when the project's declared standard supports them.

SKILL.md

44.3 KB, as published. Nobody here has run it

C Best Practices

Comprehensive guide for writing high-quality, memory-safe, portable C code. Contains 185 rules across 15 categories, prioritized by impact. C has no ownership system, no borrow checker, no destructors, and no bounds checking — nearly every correctness guarantee that other languages give you for free must be maintained by convention and discipline in C. Project constraints override generic defaults: preserve the declared C standard version, target platform assumptions, and existing error-handling conventions unless the user explicitly requests a modernization or migration.

When to Apply

Reference these guidelines when:

  • Writing new C functions, structs, or modules
  • Implementing manual memory management (allocation, ownership, cleanup)
  • Designing public C library APIs and headers
  • Reviewing code for buffer overflows, use-after-free, or undefined behavior
  • Handling errors via return codes, errno, or the goto-cleanup pattern
  • Writing multi-threaded C code (pthreads, atomics)
  • Optimizing hot paths or reducing allocation overhead
  • Refactoring legacy C code toward a modern standard
  • Setting up compiler warnings, sanitizers, and static analysis in CI

Modern C: C11/C17/C23 Features Worth Using

C has evolved substantially since C89/C99. For an existing codebase, preserve its declared standard version (-std=c99, -std=c11, -std=c17, -std=c23) unless a modernization is explicitly in scope. For new code, default to -std=c17 (widely supported, stable) or -std=c23 where the toolchain is confirmed to support it, and apply these features where the project's standard allows:

/* C11 */
_Static_assert(sizeof(int) == 4, "this code assumes 32-bit int");   /* static_assert since C23 is a keyword */
_Generic((x), int: handle_int, double: handle_double)(x);            /* type-generic dispatch */
_Thread_local int counter;                                            /* thread-local storage */
_Alignas(64) struct cache_line_data data;                              /* explicit alignment */
#include <stdatomic.h>                                                  /* atomic types and operations */

/* C99, foundational and universally supported today */
struct point p = { .x = 1, .y = 2 };   /* designated initializers */
#include <stdint.h>                       /* int32_t, uint64_t, etc. — fixed-width types */
#include <stdbool.h>                       /* bool, true, false */
int arr[n];                                 /* variable-length arrays: use with caution, see mem-stack-vs-heap */

/* C23 */
bool ok = true;              /* bool/true/false/nullptr are now keywords, no #include needed */
nullptr_t np = nullptr;        /* type-safe null pointer constant, distinct from integer 0 */
constexpr int max = 100;         /* true compile-time constant, stronger than #define or const */
#embed "data.bin"                  /* embed binary file contents directly as an initializer list */
[[nodiscard]] int must_check(void);  /* standard attribute, replaces compiler-specific warn_unused_result */

Annex K's _s-suffixed "bounds-checking interfaces" (strcpy_s, memcpy_s, ...) are part of the C11/C17 standard but remain optional for implementations to provide, and adoption is inconsistent: glibc has never implemented them, while Microsoft's CRT provides its own similar-but-not-identical _s functions. Do not rely on Annex K being available; prefer the well-supported bounded alternatives this skill recommends (snprintf, strlcpy where available, explicit length-checked helpers) instead.

For the authoritative, complete feature list per standard, consult the ISO C standard drafts (N1570 for C11, N2310 for C23) or your compiler's C conformance documentation. Everything below applies across standard versions; prefer the modern forms above where the project's declared standard supports them.

Rule Categories by Priority

PriorityCategoryImpactPrefixRules
1Memory Management & SafetyCRITICALmem-16
2Pointers & ArraysCRITICALptr-14
3Error HandlingCRITICALerr-12
4Undefined Behavior AvoidanceCRITICALub-14
5ConcurrencyCRITICALconc-10
6API/Interface DesignHIGHapi-14
7String HandlingHIGHstr-12
8Naming ConventionsMEDIUMname-12
9Type SafetyMEDIUMtype-12
10TestingMEDIUMtest-12
11DocumentationMEDIUMdoc-10
12Performance PatternsMEDIUMperf-11
13Project StructureLOWproj-10
14Linting & Static AnalysisLOWlint-10
15Anti-patternsREFERENCEanti-16

Quick Reference

1. Memory Management & Safety (CRITICAL)

2. Pointers & Arrays (CRITICAL)

3. Error Handling (CRITICAL)

  • err-assert-vs-runtime-check - Use assert() for programmer errors and invariants you control; use runtime error handling for anything derived from external input
  • err-check-return-values - Check the return value of every function that can fail, including "boring" ones like close, write, and fclose
  • err-consistent-return-codes - Pick one return-code convention per module/library and apply it consistently
  • err-document-error-contract - Document, in the header, exactly which error codes a function can return and what each one means
  • err-errno-usage - Read errno only immediately after a call that failed, and never assume it was reset to zero on success
  • err-error-enum-not-magic-int - Represent error codes as a named enum, not bare integer literals
  • err-fail-fast-invariant - Abort immediately when an internal invariant is violated, rather than continuing with corrupted state
  • err-goto-cleanup-single-exit - Use goto to jump forward to a single cleanup section when a function acquires multiple resources
  • err-negative-errno-convention - When adopting the negative-errno return convention, return -errno_value on failure and never mix it with -1/errno in the same API
  • err-out-param-for-result - Return the status code from the function and hand back the actual result through an output parameter
  • err-partial-init-rollback - When a multi-step initialization fails partway through, roll back exactly the steps that already succeeded
  • err-perror-strerror - Report system-call failures with strerror/perror (or thread-safe strerror_r), not a bare error number

4. Undefined Behavior Avoidance (CRITICAL)

  • ub-cast-away-const - Never cast away const and then write through the resulting pointer
  • ub-format-string-mismatch - Every printf/scanf-family format specifier must exactly match the type of its corresponding argument
  • ub-indeterminate-padding-bits - Never rely on the contents of struct padding bytes, and zero them explicitly before comparing, hashing, or transmitting a struct
  • ub-integer-division-by-zero - Check the divisor before performing integer division or modulo; division by zero is undefined behavior for integers
  • ub-invalid-function-pointer-cast - Never call a function through a function pointer cast to an incompatible function type
  • ub-modifying-string-literal - Never write through a pointer to a string literal; string literals may be stored in read-only memory
  • ub-null-pointer-arithmetic - Never perform pointer arithmetic on a NULL pointer, including NULL + 0
  • ub-out-of-bounds-access - Accessing an array or buffer outside its allocated bounds is undefined behavior, regardless of whether it "seems to work"
  • ub-restrict-correctness - Never mark a pointer parameter restrict if a caller can supply overlapping/aliased memory for it
  • ub-sequence-point-violation - Never modify a variable more than once, or read and modify it in an unsequenced way, between sequence points
  • ub-shift-by-invalid-amount - Never shift a value by a negative amount or by an amount greater than or equal to its type's bit width
  • ub-signed-integer-overflow - Never let a signed integer computation overflow; use unsigned types, wider types, or overflow-checked arithmetic instead
  • ub-strict-aliasing-rule - Never access an object through a pointer of an incompatible type; the compiler is allowed to assume this never happens
  • ub-uninitialized-variable-read - Reading an automatic (stack) variable before it has been assigned a value is undefined behavior

5. Concurrency (CRITICAL)

  • conc-atomic-for-flags-counters - Use C11 _Atomic (or <stdatomic.h>) for simple shared flags and counters instead of a mutex
  • conc-atomic-memory-order - Choose the weakest memory order that is still correct for each atomic operation, and default to memory_order_seq_cst when unsure
  • conc-avoid-data-races - Treat any variable touched by more than one thread as requiring explicit synchronization, with no implicit exceptions
  • conc-avoid-deadlock-lock-ordering - When a thread must hold more than one lock at a time, always acquire them in the same global order everywhere
  • conc-condvar-wait-predicate - Always wait on a condition variable inside a loop that re-checks the actual predicate, never a bare if
  • conc-mutex-protect-shared-state - Guard every piece of mutable state shared across threads with a mutex (or another synchronization primitive), no exceptions
  • conc-once-init-pthread-once - Use pthread_once (or a static local with C11's guaranteed thread-safe initialization) for one-time, thread-safe lazy initialization
  • conc-thread-create-join-discipline - Join or explicitly detach every thread you create; never let a joinable thread outlive your interest in its result silently
  • conc-thread-local-storage - Use _Thread_local (C11) for per-thread state instead of hand-rolled indexing or unsynchronized globals
  • conc-volatile-not-for-sync - Do not use volatile for thread synchronization; it prevents compiler caching but provides no atomicity or memory ordering

6. API/Interface Design (HIGH)

  • api-avoid-global-state - Prefer passing explicit state through function parameters (often a context/handle struct) over mutable global variables
  • api-callback-with-userdata - Give every callback-accepting API a void *user_data (or ctx) parameter, threaded through unchanged to the callback
  • api-consistent-prefix-naming - Prefix every public symbol in a library with a short, consistent module name
  • api-const-correct-signatures - Apply const throughout public function signatures so the API itself documents what can and cannot be mutated
  • api-error-propagation-design - Design a library's API around one propagation mechanism (return codes) and make every fallible function follow it, including "unlikely to fail" ones
  • api-header-c-linkage-guard - Wrap public C headers in extern "C" guards so they remain usable from C++ callers without name mangling issues
  • api-init-cleanup-pair - Every _create/_init/_open function must have a matching _destroy/_deinit/_close, and both must be documented together
  • api-minimal-public-surface - Expose the smallest possible set of public functions and types; make everything else static or move it to a private header
  • api-opaque-struct-encapsulation - Hide a struct's fields from consumers by exposing only a forward-declared (opaque) pointer type in the public header
  • api-out-param-convention - Order output parameters consistently (after inputs), name them with an out_/_out convention, and never write to them on failure
  • api-printf-style-format-attribute - Annotate every printf-style variadic public function with __attribute__((format(printf, ...))) (or the MSVC equivalent) so the compiler checks format strings at call sites
  • api-return-owned-vs-borrowed-doc - Document, for every function returning a pointer, whether the caller owns it (must free) or is only borrowing it (must not free, may not outlive the source)
  • api-single-responsibility-function - Give each public function exactly one responsibility, and split functions that both compute and have side effects into separate calls where practical
  • api-stable-abi-layout - For a shared library with a versioned ABI, avoid changing struct layout or function signatures in ways that break binary compatibility

7. String Handling (HIGH)

  • str-avoid-gets - Never use gets(); it was removed from the C standard entirely because it cannot be used safely
  • str-avoid-scanf-unbounded - Always specify a field width with %s/%[...] in scanf-family calls; an unbounded %s is as unsafe as gets()
  • str-avoid-sprintf-use-snprintf - Use snprintf instead of sprintf, and always check its return value against the destination buffer size
  • str-avoid-strcpy-strcat - Avoid strcpy/strcat; use a bounded alternative that takes the destination buffer's size
  • str-buffer-size-discipline - Always pass a buffer's size alongside its pointer, computed with sizeof at the buffer's declaration site, never as a separately-tracked magic number
  • str-compare-with-strncmp - Use strncmp/memcmp with an explicit, known length when comparing strings whose length you already control, instead of unbounded strcmp
  • str-null-termination-invariant - Maintain the C string invariant everywhere: every byte buffer treated as a string must have a '\0' within its bounds before any str* function touches it
  • str-safe-string-copy-pattern - Standardize on one bounded, always-null-terminating copy helper and use it everywhere instead of ad hoc strcpy/strncpy calls
  • str-string-building-dynamic - Build large or unbounded strings with a growable buffer that tracks length and capacity, not repeated fixed-size strcat/snprintf into a static buffer
  • str-strlen-cost-awareness - Remember strlen() is O(n); cache the length instead of recomputing it repeatedly in a loop
  • str-strncpy-null-termination-footgun - strncpy does not guarantee null-termination and pads the remainder with zeros; handle both surprises explicitly or avoid it
  • str-utf8-byte-vs-char - Never assume one char equals one displayed character; treat UTF-8 text as a byte sequence and use a proper library for character-level operations

8. Naming Conventions (MEDIUM)

  • name-avoid-abbreviation-ambiguity - Avoid cryptic or ambiguous abbreviations in identifiers; spell out names unless the abbreviation is truly universal in context
  • name-avoid-reserved-identifiers - Never name your own identifiers with a leading underscore, or a leading underscore followed by a capital letter or another underscore — those are reserved to the C implementation
  • name-boolean-is-has-prefix - Name boolean-returning functions and variables with an is_/has_/can_/should_ prefix so their meaning is unambiguous at every call site
  • name-consistent-module-prefix - Apply the same short module prefix to every public function, type, and constant belonging to that module, without exception
  • name-enum-constant-prefix - Prefix every enumerator with the enum's own name so its origin and intent are clear wherever it's used, since C enum constants share the global namespace
  • name-header-guard-naming - Name include guards after the full relative header path in ALL_CAPS_WITH_UNDERSCORES, so guard names never collide across a project
  • name-macro-all-caps - Name object-like and function-like macros in ALL_CAPS_WITH_UNDERSCORES to visually distinguish them from ordinary functions and variables
  • name-pointer-variable-suffix - Adopt a lightweight, optional naming signal for pointer variables (e.g. a p/ptr prefix or suffix) only when it measurably improves clarity, and apply it consistently if you do
  • name-snake-case-functions - Use lower_snake_case for function and variable names, matching the convention used by the C standard library and most C codebases
  • name-static-file-scope-prefix - Adopt a lightweight naming signal (or at minimum, consistent use of static) so internal-linkage helpers are visually distinguishable from the module's public API
  • name-struct-typedef-convention - Pick one consistent convention for naming structs and their typedefs, and apply it project-wide: either typedef struct foo foo; or a distinguishing suffix, never both styles mixed
  • name-verb-noun-function-names - Name functions as verb_noun (or module_verb_noun) so the name alone communicates the action performed

9. Type Safety (MEDIUM)

  • type-avoid-implicit-int - Always write an explicit return type and explicit parameter types; never rely on old, now-removed "implicit int" defaults
  • type-avoid-implicit-narrowing - Make narrowing conversions (wide type to narrow type, e.g. long to int) explicit, and check the value's range before converting when data loss would be a bug
  • type-avoid-plain-char-arithmetic - Cast to unsigned char before passing a char to functions like toupper/isdigit, or before using it as an array index; plain char's signedness is implementation-defined
  • type-bool-stdbool - Use bool from <stdbool.h> (C99) for boolean values, not a bare int with implied 0/1 meaning
  • type-const-correctness - Apply const to every variable, parameter, and pointee that is not intentionally mutated, throughout the codebase, not just at public API boundaries
  • type-enum-for-closed-sets - Represent a fixed, closed set of named states or options with an enum, not a bare int with implied meanings
  • type-fixed-width-stdint - Use <stdint.h> fixed-width types (int32_t, uint64_t, ...) whenever a value's exact size matters, instead of int/long/short
  • type-generic-macro - Use C11 _Generic to write type-safe, type-dispatching macros instead of unsafe function-like macros or void-pointer-based generic functions
  • type-size-t-for-sizes - Use size_t for sizes, counts, and indices, matching what sizeof, strlen, and the allocation functions already return
  • type-static-assert-invariants - Use static_assert (C11, standard keyword in C23) to verify type-layout and configuration invariants at compile time instead of discovering violations at runtime
  • type-struct-designated-init - Use C99 designated initializers to initialize structs by field name, rather than positional initialization
  • type-volatile-for-hardware-mmio - Use volatile for memory-mapped hardware registers and signal-handler-shared variables, and understand that it is not a concurrency primitive

10. Testing (MEDIUM)

  • test-arrange-act-assert-c - Structure every C test in three clear phases — arrange (set up inputs), act (call the function under test), assert (check the result) — with a blank line between them
  • test-assert-based-harness - For small projects, a minimal assert-based test harness is an acceptable, honest alternative to a full framework, as long as it reports aggregate results
  • test-boundary-value-testing - Write explicit tests for boundary values — zero, one, the maximum, the minimum, empty, and off-by-one neighbors — not just "typical" inputs
  • test-ci-matrix-compilers - Run the test suite in CI across multiple compilers (GCC and Clang, at minimum) and at least two C standard versions
  • test-coverage-gcov - Measure test coverage with gcov/llvm-cov and use it to find untested code paths, not as a target to game
  • test-descriptive-test-names - Name each test function after the specific behavior it verifies, in the form test_<unit>_<condition>_<expected_result>
  • test-fuzz-entry-point - Expose a dedicated fuzz-testing entry point (LLVMFuzzerTestOneInput) for parsers and anything that handles untrusted input
  • test-integration-test-separate-binary - Build integration tests as a separate test binary/executable from unit tests, linking against the library rather than duplicating its source
  • test-mock-via-function-pointers - Inject dependencies (I/O, time, randomness) through function pointers or a small interface struct so tests can substitute fakes
  • test-sanitizers-in-test-ci - Run the full test suite under AddressSanitizer and UndefinedBehaviorSanitizer on every CI build, not just occasionally by hand
  • test-static-functions-via-include - Test static (internal-linkage) helper functions either by #include-ing the .c file directly into a test-only translation unit, or by exposing them through a test-only internal header
  • test-unit-test-framework - Use an established C unit-testing framework (Unity, Check, or CMocka) instead of ad hoc printf-based assertions

11. Documentation (MEDIUM)

  • doc-changelog-versioning - Maintain a changelog documenting every public-API-visible change, tagged against the library's version number, especially breaking changes
  • doc-comment-why-not-what - Write comments that explain why the code does something non-obvious, not comments that just restate what the code already says
  • doc-document-error-conditions - Enumerate every specific error condition a function can produce in its documentation, not just "may fail"
  • doc-document-ownership-lifetime - Document, in the comment for every function that returns or accepts a pointer, exactly who owns the memory and how long it remains valid
  • doc-doxygen-function-comments - Document every public function with a Doxygen-style comment covering its purpose, parameters, return value, and error conditions
  • doc-example-usage-in-header - Include a short, realistic usage example in the header comment for any non-trivial public API, especially ones with a specific required call order
  • doc-header-comment-convention - Start every source and header file with a brief comment stating its purpose, and keep it current as the file's role changes
  • doc-module-level-overview-comment - Give every module (a header plus its .c file(s)) a top-level overview comment describing its responsibilities, key types, and how it fits into the larger system
  • doc-thread-safety-notes - State explicitly, for every public function and type, whether it is safe to call/access concurrently from multiple threads
  • doc-todo-fixme-convention - Mark known-incomplete or known-broken code with a consistent, greppable TODO/FIXME tag that includes an owner or issue reference

12. Performance Patterns (MEDIUM)

  • perf-avoid-alloc-in-hot-loop - Hoist allocation out of hot loops; allocate once before the loop and reuse the buffer, or use a pool/arena
  • perf-avoid-false-sharing - Pad or align per-thread data so independently-updated fields don't share the same CPU cache line
  • perf-branch-prediction-hints - Use __builtin_expect (or C++20/C23-style [[likely]]/[[unlikely]] attributes where available) to hint rare error branches to the compiler, only after profiling shows it matters
  • perf-cache-friendly-struct-layout - Order struct fields and lay out arrays so the data actually accessed together in hot code lives close together in memory
  • perf-const-for-optimizer - Mark values const (and pointers-to-data const-qualified) wherever true, giving the optimizer more freedom to cache, reorder, and avoid redundant reloads
  • perf-inline-small-functions - Mark small, frequently-called functions static inline (typically in a header) to let the compiler eliminate call overhead, and let the compiler override you when it disagrees
  • perf-loop-invariant-hoisting - Move computation that doesn't change between loop iterations outside the loop, even though optimizing compilers often do this automatically
  • perf-minimize-copies-pass-by-pointer - Pass large structs by const pointer rather than by value, to avoid copying their full contents on every call
  • perf-profile-before-optimize - Profile with real workloads before optimizing anything; intuition about where time is spent in C code is frequently wrong
  • perf-restrict-optimizer-hint - Add restrict to pointer parameters in hot numeric loops once you've verified no aliasing, to let the compiler vectorize more aggressively
  • perf-struct-of-arrays - For hot loops that process one field across many objects, prefer a struct-of-arrays (SoA) layout over an array-of-structs (AoS) layout

13. Project Structure (LOW)

  • proj-avoid-circular-includes - Never let two headers #include each other directly or indirectly; break the cycle with forward declarations or by extracting shared types into a third header
  • proj-build-system-cmake-makefile - Use a real build system (CMake or a well-structured Makefile) with explicit warning/sanitizer flags, rather than ad hoc compile-and-run commands
  • proj-consistent-directory-layout - Adopt a conventional, predictable directory layout (src/, include/, tests/, docs/) so contributors and tooling can find things without asking
  • proj-header-source-split - Separate a module's public declarations (.h) from its implementation (.c), and keep only what consumers genuinely need in the header
  • proj-include-what-you-use - #include exactly the headers a file directly uses symbols from — never rely on a symbol being transitively available through another header
  • proj-internal-header-naming - Name and locate internal-only headers so they are obviously not part of the public API — e.g. an internal/ subdirectory or an _internal.h suffix
  • proj-one-module-per-file - Keep each .c/.h pair focused on a single, cohesive responsibility; split a file once it accumulates more than one clear reason to change
  • proj-public-vs-private-headers-dir - Physically separate a library's public headers (installed, part of the API) from its private/internal headers (never installed) using distinct directories
  • proj-single-header-library-tradeoffs - Use the single-header-library pattern (STB_IMPLEMENTATION-style) deliberately, understanding its build-time and compile-time trade-offs, rather than as a default distribution format
  • proj-versioned-public-header - Expose a library's version number programmatically through its public header, not just in documentation or a build script

14. Linting & Static Analysis (LOW)

  • lint-address-sanitizer - Build and run tests with AddressSanitizer (-fsanitize=address) to detect buffer overflows, use-after-free, and double-free at the exact point they occur
  • lint-clang-tidy-checks - Run clang-tidy with a curated check set in CI to catch bug patterns and style issues beyond what compiler warnings cover
  • lint-cppcheck-static-analysis - Run cppcheck in CI as a fast, low-false-positive complement to clang-tidy and compiler warnings
  • lint-enable-wall-wextra-wpedantic - Compile every C project with -Wall -Wextra -Wpedantic at minimum, as a non-negotiable baseline
  • lint-memory-sanitizer - Use MemorySanitizer (-fsanitize=memory, Clang-only) to detect reads of uninitialized memory that other tools miss
  • lint-scan-build-clang-analyzer - Run Clang's path-sensitive static analyzer (scan-build) periodically to find deep, cross-function bugs that pattern-based linters miss
  • lint-static-analysis-in-ci - Run static analysis (clang-tidy, cppcheck, scan-build) as a required, blocking CI job, not as an optional local-only tool
  • lint-thread-sanitizer - Build and run multi-threaded test suites with ThreadSanitizer (-fsanitize=thread) to detect data races directly
  • lint-undefined-behavior-sanitizer - Build and run tests with UndefinedBehaviorSanitizer (-fsanitize=undefined) to catch signed overflow, misaligned access, invalid casts, and other UB at runtime
  • lint-werror-in-ci - Build with -Werror in CI (though not necessarily in every local dev build) so warnings cannot silently accumulate

15. Anti-patterns (REFERENCE)

  • anti-casting-malloc-return - Don't cast the return value of malloc/calloc/realloc in C; it's unnecessary and can mask a missing #include <stdlib.h>
  • anti-comparing-floats-equality - Don't compare floating-point values with ==/!=; compare against an epsilon-bounded difference (or, for exact cases, use integer/fixed-point representations)
  • anti-deeply-nested-code - Don't nest conditionals and loops more than 2-3 levels deep; use early returns/guard clauses to flatten control flow
  • anti-global-mutable-state - Don't rely on mutable global/static variables for state that should be explicit and scoped
  • anti-goto-spaghetti - Don't use goto for arbitrary jumps, backward loops, or jumping into the middle of a block; reserve it for the forward-only cleanup pattern
  • anti-huge-functions - Don't let a function grow to hundreds of lines covering multiple responsibilities; split it along natural sub-task boundaries
  • anti-ignoring-compiler-warnings - Don't ship code with unaddressed compiler warnings; treat every warning as a bug report until proven otherwise
  • anti-ignoring-syscall-return-value - Don't ignore the return value of system calls like write, read, close, and fork; each can fail or partially complete
  • anti-macro-abuse - Don't use function-like macros where a static inline function would be equally efficient and type-safe
  • anti-magic-numbers - Don't use unexplained numeric literals in code; name them as constants or enum values
  • anti-mixing-signed-unsigned-compare - Don't compare a signed and an unsigned integer directly; the signed value is implicitly converted to unsigned, which can silently invert the comparison's intent
  • anti-not-checking-snprintf-truncation - Don't ignore snprintf's return value; a return >= buffer size means the output was silently truncated
  • anti-return-stack-address - Don't return a pointer or reference to a local (automatic-storage) variable from a function
  • anti-sizeof-array-parameter - Don't call sizeof on a pointer parameter expecting the original array's size; arrays decay to pointers at function boundaries
  • anti-unchecked-malloc - Don't call malloc/calloc/realloc without checking the result for NULL
  • anti-unsafe-string-functions - Don't use gets, unbounded strcpy/strcat/sprintf, or unbounded scanf("%s", ...); use their bounded counterparts

Recommended Build Configuration

Makefile

CC      = cc
STD     = -std=c17
WARN    = -Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion -Wformat=2
SAN     = -fsanitize=address,undefined
CFLAGS  = $(STD) $(WARN) -g -O1 $(SAN)
LDFLAGS = $(SAN)

SRCS    = $(wildcard src/*.c)
OBJS    = $(SRCS:.c=.o)

app: $(OBJS)
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS)

%.o: %.c
	$(CC) $(CFLAGS) -Iinclude -c $< -o $@

test: app
	./app

clean:
	rm -f $(OBJS) app

.PHONY: test clean

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(mylib C)

set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

add_compile_options(-Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion)

option(ENABLE_SANITIZERS "Build with ASan/UBSan" ON)
if(ENABLE_SANITIZERS)
  add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)
  add_link_options(-fsanitize=address,undefined)
endif()

add_library(mylib src/widget.c src/connection.c)
target_include_directories(mylib PUBLIC include PRIVATE src)

enable_testing()
add_executable(mylib_tests tests/unit/test_widget.c)
target_link_libraries(mylib_tests PRIVATE mylib)
add_test(NAME mylib_tests COMMAND mylib_tests)

How to Use

This skill provides rule identifiers for quick reference. When generating or reviewing C code:

  1. Check relevant category based on task type
  2. Apply rules with matching prefix
  3. Prioritize CRITICAL > HIGH > MEDIUM > LOW
  4. Read rule files in rules/ for detailed examples

Rule Application by Task

TaskPrimary Categories
New functionerr-, ptr-, name-
Memory allocation/ownershipmem-, api-
New struct/public APIapi-, type-, doc-
String/buffer handlingstr-, mem-, ub-
Error handlingerr-, api-
Multi-threaded codeconc-, mem-
Undefined-behavior auditub-, ptr-, lint-
Performance tuningperf-, mem-, ptr-
Code reviewanti-, lint-, ub-
CI/build setuplint-, test-, proj-

Related Skills

  • design-patterns - choosing and implementing GoF and idiomatic design patterns; apply its C-adaptable patterns (opaque handles, function-pointer-based strategy/visitor, object pools) alongside this skill's api- and mem- rules.
  • security-review - security-audit checklists (memory-safety, injection, unsafe-function findings) for reviewing/auditing C code; use together with this skill's mem-, ptr-, ub-, and str- categories when doing a security-focused pass.

Sources

This skill synthesizes best practices from:

  • CERT C Coding Standard
  • MISRA C:2012 (with Amendments)
  • C Programming: A Modern Approach, 2nd Edition, by K. N. King
  • Effective C: An Introduction to Professional C Programming, by Robert C. Seacord
  • ISO/IEC 9899 (the C standard): C99, C11, C17, and C23 drafts
  • POSIX.1-2017 (IEEE Std 1003.1) for system-call and threading conventions
  • Linux kernel coding style
  • Production codebases: SQLite, Redis, curl, PostgreSQL, the Linux kernel
  • Clang/GCC diagnostics documentation; AddressSanitizer/UBSan/ThreadSanitizer documentation
  • Community conventions (2024-2026)

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.