---
name: C Ecosystem
description: This skill should be used when working with C projects, "C11", "C17", "C23", "Makefile", "gcc", "clang", "valgrind", "getopt", or C language patterns. Provides comprehensive Modern C (C11-C23) patterns, memory management, and CLI development best practices.
---
Provide comprehensive patterns for Modern C language (C11/C17/C23), memory management, toolchain configuration, and CLI tool development.
ISO/IEC 9899:2011 - Major modernization with threading and type-generic features
Type-generic selection for polymorphic macros
Atomic types and operations for lock-free programming
Thread-local storage duration
Compile-time assertions
Alignment specifier for variables and types
Query alignment requirements
Function that does not return
Unnamed struct/union members
ISO/IEC 9899:2018 - Bug fix release with no new features
__STDC_VERSION__ updated to 201710L
Defect reports resolved
ATOMIC_VAR_INIT deprecated
ISO/IEC 9899:2024 - Significant modernization with C++ alignment
Null pointer constant with nullptr_t type
Type inference operator
Compile-time constant objects
Type inference for variables
0b prefix for binary literals
Single quote separator in numeric literals
[[nodiscard]], [[maybe_unused]], [[deprecated]], [[fallthrough]], [[noreturn]]
bool, true, false as keywords
embed directive for binary resource inclusion
Single argument form
Fixed-width integer types from stdint.h
Exact-width signed integers
Exact-width unsigned integers
Pointer-sized integers
Unsigned size type for memory operations
Signed pointer difference type
#include <stdint.h>
#include <stddef.h>
uint32_t compute_hash(const uint8_t \*data, size_t len);
_Generic for type-based dispatch (C11)
#define print_value(x) _Generic((x), \
int: print_int, \
double: print_double, \
char *: print_string, \
default: print_unknown \
)(x)
Implement type-safe generic interfaces without void pointers
Anonymous compound literals for in-place struct/array creation
struct point { int x, y; };
void draw(struct point p);
draw((struct point){.x = 10, .y = 20});
int \*arr = (int[]){1, 2, 3, 4, 5};
Named field initialization for structs and arrays
struct config {
int timeout;
bool verbose;
const char *name;
};
struct config cfg = {
.name = "myapp",
.timeout = 30,
.verbose = true,
};
Do you need thread-safe operations?
Use _Atomic for simple counters, pthreads for complex synchronization
Single-threaded sequential execution
Lock-free atomic operations (C11)
#include <stdatomic.h>
\_Atomic int counter = 0;
void increment(void) {
atomic_fetch_add(&counter, 1);
}
int get_count(void) {
return atomic_load(&counter);
}
Thread-local storage (C11)
#include <threads.h>
\_Thread_local int errno_local;
Common undefined behavior to avoid in C
Null pointer dereference, use-after-free, buffer overflow, double-free
Signed integer overflow, division by zero, shift beyond type width
Strict aliasing violations, type punning without union
Modifying variable twice between sequence points
Use sanitizers (ASan, UBSan) during development to detect UB
Overusing void\* for generic programming
Use \_Generic macros or code generation
Hardcoded numeric values without explanation
Use named constants with define or enum
Using unbounded string functions
Use snprintf for strings. Note: strncpy does NOT null-terminate if source exceeds size
Using sprintf without bounds checking
Use snprintf with explicit buffer size
Using gets() which has no bounds checking
Use fgets() with explicit buffer size
Using scanf with unbounded %s format
Use %Ns with explicit width or fgets followed by sscanf
Not checking malloc return value
Always check for NULL after allocation
Using user input as format string (printf(user_input))
Always use printf("%s", user_input) or puts()
What is the lifetime and access pattern of the memory?
Use stack allocation or VLA (with size limit)
Use malloc/free with clear ownership
Use arena allocator
Use pool allocator
Standard malloc/calloc/realloc/free patterns
#include <stdlib.h>
#include <string.h>
char *duplicate_string(const char *src) {
if (!src) return NULL;
size_t len = strlen(src) + 1;
char *dst = malloc(len);
if (!dst) return NULL;
memcpy(dst, src, len);
return dst;
}
Always check malloc/calloc/realloc return value for NULL
Use calloc for zero-initialized memory
After realloc, use the returned pointer (original may be invalid)
Set pointer to NULL after free to prevent use-after-free
Bulk allocation with single-point deallocation
typedef struct {
char *base;
size_t size;
size_t offset;
} Arena;
Arena arena_create(size_t size) {
Arena a = {0};
a.base = malloc(size);
if (a.base) a.size = size;
return a;
}
void *arena_alloc(Arena *a, size_t bytes) {
size_t aligned = (bytes + 7) & ~7; // 8-byte alignment
if (a->offset + aligned > a->size) return NULL;
void \*ptr = a->base + a->offset;
a->offset += aligned;
return ptr;
}
void arena_reset(Arena \*a) {
a->offset = 0;
}
void arena_destroy(Arena *a) {
free(a->base);
*a = (Arena){0};
}
Parsing, compilers, request handling - many allocations freed together
Fixed-size object allocation with O(1) alloc/free
typedef struct PoolBlock {
struct PoolBlock *next;
} PoolBlock;
typedef struct {
PoolBlock *free_list;
char *memory;
size_t object_size;
size_t capacity;
} Pool;
Pool pool_create(size_t object_size, size_t count) {
Pool p = {0};
size_t size = object_size > sizeof(PoolBlock) ? object_size : sizeof(PoolBlock);
p.memory = malloc(size \* count);
if (!p.memory) return p;
p.object_size = size;
p.capacity = count;
// Build free list
for (size_t i = 0; i < count; i++) {
PoolBlock *block = (PoolBlock *)(p.memory + i * size);
block->next = p.free_list;
p.free_list = block;
}
return p;
}
void *pool_alloc(Pool *p) {
if (!p->free_list) return NULL;
PoolBlock \*block = p->free_list;
p->free_list = block->next;
return block;
}
void pool_free(Pool *p, void *ptr) {
PoolBlock \*block = ptr;
block->next = p->free_list;
p->free_list = block;
}
void pool_destroy(Pool *p) {
free(p->memory);
*p = (Pool){0};
}
Game entities, network connections, fixed-size records
Resource cleanup with goto for error handling
int process_file(const char *path) {
int result = -1;
FILE *fp = NULL;
char *buffer = NULL;
fp = fopen(path, "r");
if (!fp) goto cleanup;
buffer = malloc(BUFFER_SIZE);
if (!buffer) goto cleanup;
// ... process file ...
result = 0; // Success
cleanup:
free(buffer);
if (fp) fclose(fp);
return result;
}
Single cleanup point prevents resource leaks on error paths
Forgetting to free allocated memory
Use Valgrind/ASan, establish clear ownership rules
Freeing the same pointer twice
Set pointer to NULL after free, use ownership tracking
Accessing memory after it has been freed
Clear pointers after free, use ASan for detection
Writing beyond allocated buffer bounds
Always track buffer sizes, use bounded functions
What complexity of argument parsing is needed?
Use getopt()
Use getopt_long()
Use argp (GNU extension)
POSIX standard option parsing
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int verbose = 0;
const char *output = NULL;
int opt;
while ((opt = getopt(argc, argv, "vo:h")) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'o':
output = optarg;
break;
case 'h':
printf("Usage: %s [-v] [-o output] [file...]\n", argv[0]);
return 0;
default:
fprintf(stderr, "Usage: %s [-v] [-o output] [file...]\n", argv[0]);
return 1;
}
}
// Remaining arguments: argv[optind] to argv[argc-1]
for (int i = optind; i < argc; i++) {
printf("Processing: %s\n", argv[i]);
}
return 0;
}
GNU extension for long options
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
static struct option long_options[] = {
{"verbose", no_argument, NULL, 'v'},
{"output", required_argument, NULL, 'o'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0 }
};
int main(int argc, char *argv[]) {
int verbose = 0;
const char *output = NULL;
int opt;
while ((opt = getopt_long(argc, argv, "vo:h", long_options, NULL)) != -1) {
switch (opt) {
case 'v': verbose = 1; break;
case 'o': output = optarg; break;
case 'h':
printf("Usage: %s [--verbose] [--output FILE] [file...]\n", argv[0]);
return 0;
default:
return 1;
}
}
return 0;
}
Standard exit code conventions
Successful execution
General error
Command line usage error
Command found but not executable
Command not found
Terminated by signal N
#include <stdlib.h>
#include <sysexits.h> // EX_USAGE, EX_DATAERR, etc.
int main(int argc, char \*argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
return EX_USAGE; // 64
}
FILE *fp = fopen(argv[1], "r");
if (!fp) {
perror(argv[1]);
return EX_NOINPUT; // 66
}
// ...
return EXIT_SUCCESS;
}
Graceful signal handling for clean shutdown
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
static atomic_int running = 1;
static void handle_signal(int sig) {
(void)sig;
running = 0;
}
int main(void) {
struct sigaction sa = {0};
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
while (running) {
// Main loop work
}
printf("Shutting down gracefully...\n");
return 0;
}
Use sigaction() instead of signal() for portability
Keep signal handlers minimal - set flag only
Use volatile sig_atomic_t or atomic types for signal flags
Consistent error message format
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
static const char \*progname = "myapp";
void set_progname(const char *argv0) {
const char *p = strrchr(argv0, '/');
progname = p ? p + 1 : argv0;
}
void error(const char \*fmt, ...) {
fprintf(stderr, "%s: ", progname);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
void error_errno(const char \*fmt, ...) {
int saved_errno = errno;
fprintf(stderr, "%s: ", progname);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, ": %s\n", strerror(saved_errno));
}
GNU Compiler Collection
Enable C11 standard (or c17, c23)
Comprehensive warnings
Treat warnings as errors
Warn on variable shadowing
Warn on implicit conversions
Require function prototypes
Static analysis (GCC 10+)
LLVM C compiler
Enable C11 standard (or c17, c23)
Comprehensive warnings
Treat warnings as errors
All warnings (use selectively)
Memory error detection (buffer overflow, use-after-free)
-fsanitize=address -fno-omit-frame-pointer
gcc -fsanitize=address -fno-omit-frame-pointer -g -o myapp myapp.c
Undefined behavior detection
-fsanitize=undefined
gcc -fsanitize=undefined -g -o myapp myapp.c
Data race detection
-fsanitize=thread
Cannot be combined with AddressSanitizer
Uninitialized memory read detection (Clang only)
-fsanitize=memory
Clang-based linter and static analyzer
clang-tidy src/*.c -- -std=c11
.clang-tidy
Checks: > -*,
bugprone-_,
clang-analyzer-_,
misc-_,
performance-_,
readability-\*,
-readability-identifier-length
WarningsAsErrors: '\*'
Static analysis tool for C/C++
cppcheck --enable=all --error-exitcode=1 src/
Runtime memory error detection
valgrind --leak-check=full --show-leak-kinds=all ./myapp
Memory error detection (default)
Thread error detection
Cache profiling
Call graph profiling
What testing style do you prefer?
Use Check or Unity
Use cmocka
Use Unity (smallest footprint)
Unit testing framework for C
#include <check.h>
START_TEST(test_addition) {
ck_assert_int_eq(1 + 1, 2);
}
END_TEST
Suite *math_suite(void) {
Suite *s = suite_create("Math");
TCase \*tc = tcase_create("Core");
tcase_add_test(tc, test_addition);
suite_add_tcase(s, tc);
return s;
}
int main(void) {
Suite *s = math_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
int failed = srunner_ntests_failed(sr);
srunner_free(sr);
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
What is your project's complexity and portability needs?
Use Make
Use CMake or Meson
Use Meson
Basic Makefile for C projects
CC := gcc
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -g
LDFLAGS :=
LDLIBS :=
SRCS := $(wildcard src/\*.c)
OBJS := $(SRCS:.c=.o)
TARGET := myapp
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJS) $(TARGET)
Modern CMake for C projects
cmake_minimum_required(VERSION 3.20)
project(myapp VERSION 1.0.0 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(myapp src/main.c src/utils.c)
target_include_directories(myapp PRIVATE include)
target_compile_options(myapp PRIVATE
$<$<C_COMPILER_ID:GNU,Clang>:
-Wall -Wextra -Wpedantic -Werror
>
)
For detailed CMake patterns, see cplusplus-ecosystem skill
Meson build for C projects
# meson.build
project('myapp', 'c',
version: '1.0.0',
default_options: [
'c_std=c11',
'warning_level=3',
'werror=true',
]
)
src = files('src/main.c', 'src/utils.c')
inc = include_directories('include')
executable('myapp', src, include_directories: inc)
Use Context7 MCP for up-to-date C documentation
resolve-library-id libraryName="cppreference"
get-library-docs context7CompatibleLibraryID="/websites/cppreference_com" topic="stdatomic.h"
get-library-docs context7CompatibleLibraryID="/websites/cppreference_com" topic="malloc"
Always check return values of malloc/calloc/realloc
Enable -Wall -Wextra -Werror for all builds
Run with AddressSanitizer during development
Use Valgrind before release
Use fixed-width integer types from stdint.h
Prefer snprintf over sprintf for buffer safety
Use designated initializers for struct clarity
Document ownership semantics in function comments
Use static for file-local functions and variables
Prefer const for read-only parameters
Use enum for related constants instead of define
Include what you use - minimize header dependencies
Understand C code requirements
1. Check for existing code patterns and conventions
2. Identify memory ownership requirements
3. Review header dependencies
Write safe, portable C code
1. Use appropriate C standard (C11 minimum recommended)
2. Follow memory management patterns
3. Handle all error conditions
Verify C code correctness
1. Compile with warnings enabled
2. Run with sanitizers
3. Test with Valgrind
Compiler warning about unused variable
Fix warning, maintain clean build
Valgrind reports minor memory leak
Track down and fix leak, verify with Valgrind
AddressSanitizer detects buffer overflow
Stop, fix immediately - this is a security issue
Use-after-free or double-free detected
Block operation, require immediate fix and review
Check all allocation return values
Use bounded string functions (snprintf, strncpy)
Enable compiler warnings
Run sanitizers during development
Using gets(), sprintf(), or other unsafe functions
Raw pointer arithmetic without bounds checking
Implicit type conversions that may lose data
Global mutable state when possible
Memory architecture, data structure design
C implementation with proper memory management
Buffer overflow detection, input validation
Cache optimization, memory layout, profiling with Valgrind
Navigate C codebases and header hierarchies
C documentation via /websites/cppreference_com
CMake patterns and clang-tidy configuration
Debugging with Valgrind, GDB, and sanitizers