AgentStack
SKILL verified MIT Self-run

C Patterns

skill-marvinrichter-clarc-c-patterns · by marvinrichter

C programming patterns (C11/C17): opaque pointer encapsulation, error handling via return codes and out-parameters, resource cleanup with goto, memory management discipline (malloc/free pairing, double-free prevention), safe string functions (snprintf, fgets, strncpy), Makefile patterns with auto-dependencies, bit manipulation, and AddressSanitizer/Valgrind integration. Use when writing or review…

No reviews yet
0 installs
15 views
0.0% view→install

Install

$ agentstack add skill-marvinrichter-clarc-c-patterns

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of C Patterns? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

C Patterns

When to Activate

  • Writing C source files (.c, .h)
  • Designing C module APIs (opaque pointers, error codes)
  • Reviewing C code for memory safety
  • Setting up a C project with Makefile or CMake
  • Refactoring a C module to use the opaque pointer pattern so internal struct fields are hidden from callers
  • Auditing a codebase for unsafe string functions (gets, sprintf, strcpy) that could cause buffer overflows
  • Implementing multi-resource cleanup with goto to ensure every allocation is freed on every error path
  • Integrating AddressSanitizer or Valgrind into a CI pipeline to catch memory leaks and use-after-free bugs automatically

Opaque Pointer — Module Encapsulation

The fundamental C encapsulation pattern. Callers see the type name but not its fields.

/* --- queue.h (public API) --- */
#ifndef QUEUE_H
#define QUEUE_H

#include 
#include "errors.h"

typedef struct Queue Queue;

Queue    *queue_new(size_t capacity);
void      queue_free(Queue *q);
ErrorCode queue_push(Queue *q, const void *item, size_t item_size);
ErrorCode queue_pop(Queue *q, void *out, size_t max_size);
size_t    queue_size(const Queue *q);

#endif

/* --- queue.c (private implementation) --- */
struct Queue {
    void   **items;
    size_t   capacity;
    size_t   head;
    size_t   tail;
    size_t   count;
};

Queue *queue_new(size_t capacity) {
    Queue *q = malloc(sizeof(Queue));
    if (!q) return NULL;

    q->items = malloc(capacity * sizeof(void *));
    if (!q->items) { free(q); return NULL; }

    q->capacity = capacity;
    q->head = q->tail = q->count = 0;
    return q;
}

void queue_free(Queue *q) {
    if (!q) return;
    free(q->items);
    free(q);
}

Error Code Pattern

Define an enum for all error states; return it from every fallible function:

/* errors.h */
typedef enum {
    ERR_OK        =  0,
    ERR_NOMEM     = -1,   /* malloc returned NULL */
    ERR_INVALID   = -2,   /* bad argument */
    ERR_NOT_FOUND = -3,
    ERR_IO        = -4,
    ERR_OVERFLOW  = -5,
    ERR_TIMEOUT   = -6
} ErrorCode;

const char *err_str(ErrorCode rc);  /* human-readable string */
/* Usage pattern — check every return value */
Config cfg;
ErrorCode rc = config_parse("app.conf", &cfg);
if (rc != ERR_OK) {
    fprintf(stderr, "config_parse: %s\n", err_str(rc));
    return rc;
}

goto cleanup — Multi-Resource Cleanup

The idiomatic C pattern for safe multi-resource cleanup. Label at the end; jump on failure:

ErrorCode connect_and_send(const char *host, int port, const char *data) {
    int       fd  = -1;
    char     *buf = NULL;
    ErrorCode rc  = ERR_OK;

    fd = socket_connect(host, port);
    if (fd = 0) close(fd);
    return rc;
}

Memory Management Discipline

malloc / free Pairing Rules

  1. Every malloc has exactly one matching free
  2. After free(ptr), immediately set ptr = NULL to prevent double-free
  3. free(NULL) is a no-op — safe to call unconditionally in cleanup
  4. Always check malloc return value before use
char *buf = malloc(size);
if (!buf) { return ERR_NOMEM; }

/* ... use buf ... */

free(buf);
buf = NULL;   /* prevent dangling pointer */

calloc for Zero-Initialized Memory

/* calloc zeros memory — prefer over malloc+memset for structs */
Node *node = calloc(1, sizeof(Node));
if (!node) return ERR_NOMEM;

realloc Safely

/* WRONG — leaks original buffer if realloc returns NULL */
buf = realloc(buf, new_size);

/* CORRECT — use temporary */
void *tmp = realloc(buf, new_size);
if (!tmp) { free(buf); return ERR_NOMEM; }
buf = tmp;

Safe String Functions

| Unsafe | Safe Replacement | |--------|-----------------| | gets | fgets(buf, sizeof(buf), stdin) | | sprintf | snprintf(buf, sizeof(buf), ...) | | strcpy | strncpy(dst, src, sizeof(dst)-1); dst[sizeof(dst)-1]='\0' | | strcat | strncat(dst, src, sizeof(dst)-strlen(dst)-1) | | strtok | strtok_r (reentrant) |

/* Safe string copy helper */
static void safe_strcpy(char *dst, const char *src, size_t dst_size) {
    strncpy(dst, src, dst_size - 1);
    dst[dst_size - 1] = '\0';
}

Bit Manipulation

#include 

#define BIT(n)          (1u << (n))
#define FLAG_READ       BIT(0)
#define FLAG_WRITE      BIT(1)
#define FLAG_EXECUTE    BIT(2)

/* Test, set, clear, toggle */
#define FLAG_IS_SET(f, flag)    (((f) & (flag)) != 0)
#define FLAG_SET(f, flag)       ((f) |= (flag))
#define FLAG_CLEAR(f, flag)     ((f) &= ~(flag))
#define FLAG_TOGGLE(f, flag)    ((f) ^= (flag))

/* Portable endian conversion */
uint32_t be32_to_cpu(uint32_t be) {
    const uint8_t *p = (const uint8_t *)&be;
    return ((uint32_t)p[0] << 24) |
           ((uint32_t)p[1] << 16) |
           ((uint32_t)p[2] <<  8) |
           ((uint32_t)p[3]);
}

Makefile with Auto-Dependencies

CC      = gcc
CFLAGS  = -std=c11 -Wall -Wextra -Wpedantic -Werror
CFLAGS += -MMD -MP           # generate .d dependency files

SRCS    = $(wildcard src/*.c)
OBJS    = $(SRCS:src/%.c=build/%.o)
DEPS    = $(OBJS:.o=.d)

-include $(DEPS)             # include generated dependencies

.PHONY: all test clean

all: build/app

build/%.o: src/%.c | build
	$(CC) $(CFLAGS) -Isrc -c $< -o $@

build/app: $(OBJS)
	$(CC) $(CFLAGS) $^ -o $@

# Test target with sanitizers
test: CFLAGS += -fsanitize=address,undefined -g -O1
test: build/test_runner
	./build/test_runner

build:
	@mkdir -p $@

clean:
	$(RM) -r build

AddressSanitizer + Valgrind

AddressSanitizer (compile-time, fast)

CFLAGS_ASAN = -fsanitize=address,undefined -g -O1 -fno-omit-frame-pointer

Detects: heap-use-after-free, stack-buffer-overflow, global-buffer-overflow, use-after-return, use-after-scope, undefined behavior.

Valgrind (runtime, comprehensive)

valgrind \
  --leak-check=full \
  --show-leak-kinds=all \
  --track-origins=yes \
  --error-exitcode=1 \
  ./build/test_runner

Use ASan in CI (fast), Valgrind locally for deep debugging.


Anti-Patterns

| Anti-Pattern | Problem | Better | |---|---|---| | gets(buf) | No bounds check, undefined behavior | fgets(buf, sizeof(buf), stdin) | | sprintf(buf, ...) without size | Buffer overflow | snprintf(buf, sizeof(buf), ...) | | Ignoring malloc return | NULL dereference | Always check, return ERR_NOMEM | | Double-free | Undefined behavior, security hole | Set ptr = NULL after free | | User input as printf format | Format string attack | printf("%s", user_input) | | Signed/unsigned comparison | Subtle bugs | Use size_t for sizes throughout | | Magic numbers | Unreadable | Named #define constants |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.