All skills
c-pro
Professional C: memory ownership, defensive programming, build hygiene, and systems-level correctness. Use when writing, reviewing, or maintaining C code.
Use this skill
- Read the full skill below — it’s all right here on this page. When you like it, hit copy.
- Paste it into a chat with Muse and add: “Please use this skill whenever I ask about c pro. Remember it for our future conversations.”
- That’s it. Muse follows the playbook for relevant tasks, and you approve anything it does.
The full skill
C Pro
Overview
C gives you absolute control and zero safety net: every allocation, every bound, every lifetime is your responsibility. Professional C is therefore a discipline of explicit contracts — who owns this memory, who frees it, what are the valid inputs — enforced by convention, assertions, sanitizers, and relentless simplicity.
This skill covers writing C that's correct, readable, and maintainable: ownership discipline, defensive interfaces, build hygiene, and the tooling that compensates for the language's lack of guardrails.
When to use
- Writing or reviewing C for correctness and safety.
- Designing C APIs (ownership, error reporting, lifetimes).
- Debugging memory corruption, leaks, or undefined behavior.
- Structuring C projects (build systems, modularization).
- Maintaining or hardening legacy C codebases.
Core concepts
- Ownership is a documented contract. Every allocation site answers: who frees this, and when?
Encode it in naming (
create_/destroy_,_new/_freepairs) and document it on every function that transfers ownership. Consistent conventions beat clever ones. - Defensive interfaces. Validate inputs at API boundaries (
NULLchecks, range checks, buffer sizes passed explicitly — never truststrlenon untrusted input). Return error codes or use out-params for errors; never fail silently.assertfor internal invariants (programmer errors), runtime checks for external data. - Bounded everything.
snprintfnotsprintf,strncpy-with-care (or better,snprintf), explicit lengths on every buffer operation. Buffer overflows remain the classic C vulnerability — the fix is boring and total: never write without knowing the bound. - Simple control flow, small functions. C punishes cleverness disproportionately. Flat
functions, early returns, one exit path for cleanup (
goto cleanupis idiomatic C — used consistently, it's clearer than nested conditionals for resource teardown). - Opaque types for encapsulation.
typedef struct Foo Foo;in the header, full definition in the.cfile — clients can't poke internals, and you're free to change representation. C's answer to private members. - The build is part of the program.
-Wall -Wextra -Werror -Wpedantic, sanitizers (ASan/UBSan) in test builds,-fstack-protector, and warnings reviewed — not suppressed. The compiler is your only static analyzer unless you add more (clang-tidy, cppcheck).
Practical workflow
- Set compiler flags first.
-std=c17 -Wall -Wextra -Werror -Wpedantic -gfor dev; sanitizer builds (-fsanitize=address,undefined) for tests; hardened flags for release. - Design the API before the implementation. Header first: types, ownership comments, error conventions. If the header needs a paragraph to explain, simplify the API.
- Write with the cleanup pattern. Single exit with
goto cleanupfor functions acquiring multiple resources — consistent, reviewable, exception-free RAII:int process(const char *path) { FILE *f = NULL; char *buf = NULL; int rc = -1; f = fopen(path, "r"); if (!f) goto cleanup; buf = malloc(BUF_SIZE); if (!buf) goto cleanup; /* ... work ... */ rc = 0; cleanup: free(buf); if (f) fclose(f); return rc; } - Test with sanitizers. Unit tests (cmocka/unity/greatest) run under ASan+UBSan in CI; fuzz anything parsing untrusted input (AFL++/libFuzzer).
- Check resources systematically. Every
mallochas afreeon all paths; everyfopenafclose; every lock an unlock. Review diffs specifically for the error paths — that's where leaks live. - Document the non-obvious. Thread-safety guarantees, reentrancy, signal-safety, and ownership — in the header, where callers will see it.
Common pitfalls
- Use-after-free / double-free. The classic. Clear pointers after free in complex teardown
(
free(p); p = NULL;), keep ownership single and obvious, and let ASan prove it. - Off-by-one and missing NUL.
char buf[16]; strncpywithout termination,<=vs<in loops over buffers. Write the bound, then re-read it — or better, usesnprintfand check its return. - Ignoring return values.
malloccan return NULL;freadcan short-read;snprintfreports truncation. Unchecked returns are latent crashes —-Wunused-resulthelps for annotated functions. - Undefined behavior rationalized. "It works" with signed overflow, strict-aliasing violations,
or data races. UBSan +
-fstrict-aliasingwarnings exist because the optimizer will exploit UB. - Global mutable state. Hidden coupling, untestable functions, thread-unsafety. Pass context
structs explicitly; keep globals
constor don't have them. - Clever macros. Multi-statement macros without
do { } while(0), macros with side-effecting arguments evaluated twice, macro "generics" hiding type errors. Preferstatic inlinefunctions. - No bounds on input parsing.
scanf("%s"),gets(removed from the language for a reason), hand-rolled parsers without length discipline. Parse defensively or use a tested library.