Skill

Write idiomatic, efficient C code with best-practice patterns

A before/after C reference for memory management, pointers, error handling, strings, and preprocessor patterns.

Works with c

Maintainer of this project? Claim this page to edit the listing.


91
Spark score
out of 100
Updated 26 days ago
Version 13.6.1

Add to Favorites

Why it matters

Help developers write clean, performant, and idiomatic C code by providing reference patterns, efficiency guidelines, and best-practice examples that align with modern C standards and conventions.

Outcomes

What it gets done

01

Apply idiomatic C patterns for memory management, pointers, and data structures

02

Optimize code for performance using efficient algorithms and compiler-friendly constructs

03

Follow C standard library conventions and modern C99/C11/C17 best practices

04

Structure code with proper error handling, type safety, and maintainability patterns

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/ag-c | bash

Overview

C: Idiomatic Efficiency Reference

A before/after C reference covering memory management, pointers, error handling, strings, structs, and preprocessor patterns. Use when writing or reviewing C code and want idiomatic, memory-safe patterns applied consistently.

What it does

C: Idiomatic Efficiency Reference is a skill of before/after C coding guidelines across seven areas: memory management, pointers and arrays, error handling, strings, structs and enums, preprocessor and headers, and a summary anti-pattern table.

When to use - and when NOT to

Use this when writing or reviewing C code and want idiomatic, safer patterns applied consistently. It's language-specific guidance, not an architectural framework, and the skill notes over-compression can hurt readability, so judgement still applies.

Inputs and outputs

Memory management: always check malloc's return value before use, use sizeof *ptr instead of sizeof(Type) so it stays correct when the type changes, avoid casting malloc's result since it hides a missing #include, null a pointer immediately after free, and use a single cleanup label with goto for multi-allocation functions instead of leaking on early-return paths. Pointers and arrays: pass array length alongside a pointer or bundle both into a struct rather than tracking size manually, prefer arr[i] indexing over raw pointer arithmetic, and avoid VLAs in production code, a stack overflow risk, in favor of heap allocation with an error check. Error handling: use named error codes via errno/perror instead of magic numbers, and use early-return or goto cleanup instead of deeply nested if blocks - goto cleanup is described as idiomatic C for resource teardown, not something to avoid on principle. Strings: replace strcpy with strncpy plus explicit null-termination or snprintf, never compare string content with ==, since that compares pointers, and avoid repeated strcat in a loop, an O(n squared) pattern, in favor of tracking a write position with snprintf. Structs and enums: typedef structs to avoid repeating the struct keyword, always zero-initialize a struct rather than leaving it uninitialized, and use named enums instead of magic integer constants for state values.

Integrations

Preprocessor and headers: prefer a static inline function over a macro for logic, since macros lack type safety and can double-evaluate arguments like x++, and always use an #ifndef/#define include guard, or the widely supported but non-standard #pragma once. A closing anti-pattern table maps specific bad patterns - sprintf, gets, casting malloc's result, VLAs for large arrays, void* callbacks with no context param, global mutable state, assert used for runtime error handling, missing const on read-only pointer params, and mixing signed/unsigned comparisons - to their preferred replacements.

Who it's for

C developers writing or reviewing code who want a concrete before/after reference for memory safety, string handling, and idiomatic patterns instead of relying on habit or memory.

Source README

C: Idiomatic Efficiency Reference

When to Use

  • Use this skill when the task matches this description: Language-specific super-code guidelines for c.

Table of Contents

  1. Memory Management
  2. Pointers & Arrays
  3. Error Handling
  4. Strings
  5. Structs & Enums
  6. Preprocessor & Headers
  7. Anti-patterns specific to C

1. Memory Management {#memory}

// ❌ malloc without checking return value
char *buf = malloc(size);
strcpy(buf, src);

// ✅
char *buf = malloc(size);
if (!buf) return -ENOMEM;
memcpy(buf, src, size);
// ❌ Casting malloc result (unnecessary in C, hides missing #include)
int *p = (int *)malloc(n * sizeof(int));

// ✅
int *p = malloc(n * sizeof *p);
// ❌ free without nulling (dangling pointer risk in long-lived scope)
free(ptr);
// ... later code might use ptr

// ✅
free(ptr);
ptr = NULL;
// ❌ Forgetting to free on early-return paths
char *a = malloc(100);
char *b = malloc(200);
if (!b) return -1; // leaks a

// ✅ — single cleanup label
char *a = NULL, *b = NULL;
a = malloc(100);
if (!a) goto cleanup;
b = malloc(200);
if (!b) goto cleanup;
// ... use a, b ...
cleanup:
    free(b);
    free(a);

Use sizeof *ptr instead of sizeof(Type) - it stays correct when the type changes.


2. Pointers & Arrays {#pointers}

// ❌ Manual array size tracking
void process(int *arr, int len) { ... }
process(data, 10);

// ✅ — pass size alongside pointer, or use a struct
typedef struct { int *data; size_t len; } IntSlice;
// ❌ Pointer arithmetic where array indexing is clearer
*(arr + i) = value;

// ✅
arr[i] = value;
// ❌ VLA in production code (stack overflow risk, optional in C11+)
int arr[n];

// ✅
int *arr = malloc(n * sizeof *arr);
if (!arr) return -ENOMEM;
// ... use arr ...
free(arr);

3. Error Handling {#errors}

// ❌ Using magic numbers for error returns
if (do_thing() == -1) { ... }

// ✅ — define or use named error codes
#include <errno.h>
if (do_thing() < 0) {
    perror("do_thing");
    return errno;
}
// ❌ Deeply nested error checks
int r1 = step1();
if (r1 == 0) {
    int r2 = step2();
    if (r2 == 0) {
        int r3 = step3();
        // ...
    }
}

// ✅ — early return / goto cleanup
if (step1() < 0) goto fail;
if (step2() < 0) goto fail;
if (step3() < 0) goto fail;
return 0;
fail:
    cleanup();
    return -1;

goto cleanup is idiomatic C for resource teardown - don't avoid it out of principle.


4. Strings {#strings}

// ❌ strcpy without bounds checking
strcpy(dest, src);

// ✅
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
// or better: snprintf(dest, sizeof(dest), "%s", src);
// ❌ strcmp misuse
if (str == "hello") { ... } // compares pointers, not content

// ✅
if (strcmp(str, "hello") == 0) { ... }
// ❌ Building strings with repeated strcat (O(n²))
char result[1024] = "";
for (int i = 0; i < n; i++) {
    strcat(result, items[i]);
}

// ✅ — track write position
char result[1024];
int pos = 0;
for (int i = 0; i < n && pos < (int)sizeof(result); i++) {
    pos += snprintf(result + pos, sizeof(result) - pos, "%s", items[i]);
}

Prefer snprintf over sprintf - always.


5. Structs & Enums {#structs}

// ❌ Bare struct requiring `struct` keyword everywhere
struct point { int x, y; };
struct point p = {1, 2};

// ✅
typedef struct { int x, y; } Point;
Point p = {1, 2};
// ❌ Uninitialized struct
Point p;
use(p.x); // UB

// ✅
Point p = {0};
// ❌ Magic integer constants
if (state == 3) { ... }

// ✅
typedef enum { STATE_IDLE, STATE_RUNNING, STATE_DONE } State;
if (state == STATE_DONE) { ... }

6. Preprocessor & Headers {#preprocessor}

// ❌ Macro where inline function works (no type safety, double eval)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
MAX(x++, y) // x incremented twice if x > y

// ✅
static inline int max_int(int a, int b) { return a > b ? a : b; }
// ❌ No include guard
// my_header.h
struct Foo { int x; };

// ✅
#ifndef MY_HEADER_H
#define MY_HEADER_H
struct Foo { int x; };
#endif
// or: #pragma once (widely supported, not standard)

Keep macros for conditional compilation and constants. Use static inline for logic.


7. Anti-patterns specific to C {#antipatterns}

Anti-pattern Preferred
sprintf snprintf with buffer size
gets fgets (gets is removed in C11)
Casting malloc result let implicit void* conversion work
sizeof(Type) in malloc sizeof *ptr
VLA for large/runtime arrays heap allocation
void* callbacks without context param pass void *ctx alongside function pointer
Global mutable state pass state through struct pointers
assert for runtime error handling proper error return codes
Missing const on read-only pointer params const char *str
Mixing signed/unsigned in comparisons use consistent types, cast explicitly

Limitations

  • These are language-specific guidelines and do not cover overall architectural decisions.
  • Over-compression might reduce readability; apply judgement.

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.