Claude-skill-registry lang-c-dev
Foundational C programming patterns covering type system, memory management, pointers, arrays, preprocessor, compilation, concurrency (pthreads, mutexes, atomics), serialization (binary, JSON, struct packing), and testing (Unity, CMocka, Check). Complete 8/8 pillar coverage. Use when writing C code, understanding manual memory management, working with system-level programming, or needing guidance on which specialized C skill to use. This is the entry point for C development.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lang-c-dev" ~/.claude/skills/majiayu000-claude-skill-registry-lang-c-dev && rm -rf "$T"
skills/data/lang-c-dev/SKILL.mdC Fundamentals
Foundational C programming patterns and core language features. This skill serves as both a reference for common patterns and an index to specialized C skills.
Overview
┌─────────────────────────────────────────────────────────────────┐ │ C Skill Hierarchy │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────┐ │ │ │ lang-c-dev │ ◄── You are here │ │ │ (foundation) │ │ │ └────────┬─────────┘ │ │ │ │ │ ┌────────────┬───────────┼───────────┬────────────┐ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ │ │ ┌────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐ ┌──────────┐ │ │ │ memory │ │ posix │ │library │ │ systems │ │ embedded │ │ │ │ -eng │ │ -dev │ │ -dev │ │ -eng │ │ -dev │ │ │ └────────┘ └──────────┘ └────────┘ └─────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘
This skill covers:
- Type system (primitives, structs, unions, enums)
- Pointers and memory management (malloc, free, addresses)
- Arrays and strings (null-terminated, manipulation)
- Preprocessor directives (#define, #include, macros)
- Header files and compilation process
- Concurrency (pthreads, mutexes, condition variables, atomics)
- Serialization (binary, JSON with cJSON, struct packing)
- Testing (Unity, CMocka, Check frameworks)
- Common idioms and safety patterns
This skill does NOT cover (see specialized skills):
- Advanced memory engineering →
lang-c-memory-eng - POSIX APIs and system calls →
lang-c-posix-dev - Library/package creation →
lang-c-library-dev - Systems programming patterns →
lang-c-systems-eng - Embedded programming →
lang-c-embedded-dev
Quick Reference
| Task | Pattern |
|---|---|
| Declare variable | |
| Declare pointer | |
| Allocate memory | |
| Free memory | |
| Define macro | |
| Include header | or |
| Declare function | |
| Define struct | |
| Access member | or |
Skill Routing
Use this table to find the right specialized skill:
| When you need to... | Use this skill |
|---|---|
| Understand memory safety, buffer overflows | |
| Work with POSIX APIs, file I/O, processes | |
| Create static/dynamic libraries | |
| System-level programming, signals, IPC | |
| Embedded systems, bare-metal programming | |
Type System
Primitive Types
// Integer types char c = 'A'; // 1 byte, -128 to 127 unsigned char uc = 255; // 1 byte, 0 to 255 short s = -1000; // 2 bytes (typically) unsigned short us = 60000; // 2 bytes int i = -100000; // 4 bytes (typically) unsigned int ui = 100000; // 4 bytes long l = -1000000L; // 4 or 8 bytes unsigned long ul = 1000000UL; long long ll = -9223372036854775807LL; // 8 bytes // Floating-point types float f = 3.14f; // 4 bytes, ~7 decimal digits double d = 3.141592653589793; // 8 bytes, ~15 decimal digits long double ld = 3.14159265358979323846L; // 10-16 bytes // Boolean (C99+) #include <stdbool.h> bool flag = true; // Requires stdbool.h // Size-specific types (C99+, stdint.h) #include <stdint.h> int8_t i8 = -128; // Exactly 8 bits uint8_t u8 = 255; // Exactly 8 bits int16_t i16 = -32768; // Exactly 16 bits uint16_t u16 = 65535; // Exactly 16 bits int32_t i32 = -2147483648; // Exactly 32 bits uint32_t u32 = 4294967295U; // Exactly 32 bits int64_t i64 = -9223372036854775807LL; // Exactly 64 bits uint64_t u64 = 18446744073709551615ULL; // Exactly 64 bits // Pointer-sized integers intptr_t iptr; // Pointer-sized signed uintptr_t uptr; // Pointer-sized unsigned size_t sz; // For sizes, unsigned
Type Qualifiers
// const: Value cannot be modified const int max = 100; const char *str = "Hello"; // Pointer to const char char *const ptr = buf; // Const pointer to char const char *const cptr = "Fixed"; // Both const // volatile: Value may change unexpectedly (hardware registers, signal handlers) volatile int hardware_register; volatile sig_atomic_t signal_flag; // restrict (C99+): Pointer is only way to access object (optimization hint) void copy(char *restrict dest, const char *restrict src, size_t n);
Structs
// Basic struct definition struct Point { int x; int y; }; // Creating instances struct Point p1 = {10, 20}; // Designated order struct Point p2 = {.y = 30, .x = 15}; // Designated initializers (C99+) // Accessing members p1.x = 5; p1.y = 10; // Typedef for convenience typedef struct Point Point; // Or combined: typedef struct { int x; int y; } Point; // Now can use: Point p; Point p3 = {1, 2}; // Nested structs typedef struct { Point top_left; Point bottom_right; } Rectangle; // Struct with flexible array member (C99+) typedef struct { size_t len; char data[]; // Must be last member } String; // Allocate with variable size String *str = malloc(sizeof(String) + 100); str->len = 100;
Unions
// Union: Members share same memory union Data { int i; float f; char str[20]; }; // All members occupy same space; only one valid at a time union Data d; d.i = 10; // Store integer printf("%d\n", d.i); d.f = 220.5f; // Now integer is invalid printf("%f\n", d.f); // Common use: Tagged unions for type-safe variants typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } DataType; typedef struct { DataType type; union { int i; float f; char *str; } value; } Variant; // Safe access void print_variant(Variant *v) { switch (v->type) { case TYPE_INT: printf("%d\n", v->value.i); break; case TYPE_FLOAT: printf("%f\n", v->value.f); break; case TYPE_STRING: printf("%s\n", v->value.str); break; } }
Enums
// Basic enum enum Color { RED, // 0 GREEN, // 1 BLUE // 2 }; // With explicit values enum Status { OK = 0, ERROR = -1, PENDING = 1 }; // Typedef for convenience typedef enum { MODE_READ = 0x01, MODE_WRITE = 0x02, MODE_EXECUTE = 0x04 } FileMode; // Using enums FileMode mode = MODE_READ | MODE_WRITE; // Bitwise OR // Enum as bit flags if (mode & MODE_WRITE) { // Has write permission }
Pointers and Memory Management
Pointer Basics
int x = 10; int *ptr = &x; // ptr stores address of x printf("%d\n", x); // Value: 10 printf("%p\n", &x); // Address of x printf("%p\n", ptr); // Address stored in ptr (same as &x) printf("%d\n", *ptr); // Dereference: value at address (10) *ptr = 20; // Modify x through pointer printf("%d\n", x); // x is now 20 // Pointer arithmetic int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; // Points to first element printf("%d\n", *p); // 1 printf("%d\n", *(p+1)); // 2 printf("%d\n", *(p+2)); // 3 // NULL pointer int *null_ptr = NULL; // Points to nothing if (null_ptr == NULL) { // Safe: check before dereferencing }
Dynamic Memory Allocation
#include <stdlib.h> // malloc: Allocate uninitialized memory int *numbers = malloc(10 * sizeof(int)); if (numbers == NULL) { // Allocation failed perror("malloc"); return -1; } // Use the memory for (int i = 0; i < 10; i++) { numbers[i] = i * 2; } // Free when done free(numbers); numbers = NULL; // Good practice: prevent use-after-free // calloc: Allocate zero-initialized memory int *zeros = calloc(10, sizeof(int)); // All elements = 0 free(zeros); // realloc: Resize allocation int *resized = realloc(numbers, 20 * sizeof(int)); if (resized == NULL) { // Realloc failed, original pointer still valid free(numbers); return -1; } numbers = resized; free(numbers); // Allocating structures typedef struct { char name[50]; int age; } Person; Person *person = malloc(sizeof(Person)); if (person == NULL) { return -1; } strcpy(person->name, "Alice"); person->age = 30; free(person);
Pointer Patterns
// Double pointer (pointer to pointer) void allocate_string(char **str) { *str = malloc(100); } char *buffer = NULL; allocate_string(&buffer); strcpy(buffer, "Hello"); free(buffer); // Function pointers int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int (*operation)(int, int); // Declare function pointer operation = add; printf("%d\n", operation(5, 3)); // 8 operation = subtract; printf("%d\n", operation(5, 3)); // 2 // Array of function pointers typedef int (*BinaryOp)(int, int); BinaryOp ops[] = {add, subtract}; printf("%d\n", ops[0](10, 5)); // 15 printf("%d\n", ops[1](10, 5)); // 5 // Const correctness void read_data(const int *data, size_t n) { // data[0] = 10; // Error: cannot modify printf("%d\n", data[0]); // OK: can read } void write_data(int *data, size_t n) { data[0] = 10; // OK: can modify }
Arrays and Strings
Arrays
// Fixed-size arrays int numbers[5] = {1, 2, 3, 4, 5}; char chars[10] = {'a', 'b', 'c'}; // Rest initialized to 0 // Array size size_t length = sizeof(numbers) / sizeof(numbers[0]); // 5 // Multi-dimensional arrays int matrix[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; printf("%d\n", matrix[1][2]); // 7 // Passing arrays to functions (decay to pointers) void process(int arr[], size_t n) { // arr is actually int* for (size_t i = 0; i < n; i++) { arr[i] *= 2; } } int data[5] = {1, 2, 3, 4, 5}; process(data, 5); // Dynamic arrays size_t capacity = 10; int *dynamic = malloc(capacity * sizeof(int)); // ... use dynamic array ... free(dynamic);
Strings (Null-Terminated Char Arrays)
#include <string.h> // String literals (stored in read-only memory) const char *str1 = "Hello, World!"; // Pointer to literal // Mutable string (array) char str2[] = "Hello"; // {'H','e','l','l','o','\0'} str2[0] = 'h'; // OK: modifiable // String length size_t len = strlen(str1); // 13 (doesn't count '\0') // String copy char dest[50]; strcpy(dest, "Hello"); // Unsafe: no bounds check strncpy(dest, "Hello", 49); // Safer: limit length dest[49] = '\0'; // Ensure null termination // String concatenation strcat(dest, " World"); // Unsafe strncat(dest, " World", 49 - strlen(dest)); // Safer // String comparison if (strcmp(str1, str2) == 0) { // Strings equal } if (strncmp(str1, str2, 5) == 0) { // First 5 chars equal } // String search char *pos = strchr(str1, 'W'); // Find first 'W' if (pos != NULL) { printf("Found at index: %ld\n", pos - str1); } char *sub = strstr(str1, "World"); // Find substring if (sub != NULL) { printf("Substring found\n"); } // Safe string handling (C11+) #ifdef __STDC_LIB_EXT1__ strcpy_s(dest, sizeof(dest), "Safe"); strcat_s(dest, sizeof(dest), " Copy"); #endif // Manual string building char buffer[100]; snprintf(buffer, sizeof(buffer), "Name: %s, Age: %d", "Alice", 30);
String Manipulation Patterns
// Tokenizing strings char input[] = "apple,banana,cherry"; char *token = strtok(input, ","); while (token != NULL) { printf("%s\n", token); token = strtok(NULL, ","); } // Converting strings to numbers const char *num_str = "12345"; int num = atoi(num_str); // ASCII to integer long lnum = atol(num_str); // ASCII to long double dnum = atof("3.14159"); // ASCII to float // Better conversion with error checking (C99+) char *endptr; long val = strtol(num_str, &endptr, 10); // Base 10 if (endptr == num_str) { printf("No conversion performed\n"); } else if (*endptr != '\0') { printf("Partial conversion: stopped at '%s'\n", endptr); } // Formatting strings char output[100]; int written = snprintf(output, sizeof(output), "x=%d, y=%f, s=%s", 10, 3.14, "test"); if (written >= sizeof(output)) { printf("Output truncated\n"); }
Preprocessor
Include Directives
// System headers (search system paths) #include <stdio.h> #include <stdlib.h> #include <string.h> // User headers (search current directory first) #include "myheader.h" #include "utils/helper.h"
Macros
// Object-like macros #define PI 3.14159 #define MAX_SIZE 1024 #define VERSION "1.0.0" // Function-like macros #define SQUARE(x) ((x) * (x)) // Parentheses important! #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) // Multi-line macros #define SWAP(a, b, type) do { \ type temp = (a); \ (a) = (b); \ (b) = temp; \ } while(0) // Usage int x = 5, y = 10; SWAP(x, y, int); // Stringification #define STRINGIFY(x) #x printf("%s\n", STRINGIFY(Hello)); // Prints: Hello // Token pasting #define CONCAT(a, b) a##b int CONCAT(var, 123) = 42; // Creates: int var123 = 42; // Variadic macros (C99+) #define LOG(fmt, ...) printf("[LOG] " fmt "\n", ##__VA_ARGS__) LOG("Starting process"); LOG("Value: %d", 42); // Common utility macros #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define UNUSED(x) (void)(x) #define likely(x) __builtin_expect(!!(x), 1) // GCC optimization hint #define unlikely(x) __builtin_expect(!!(x), 0)
Conditional Compilation
// Platform-specific code #ifdef _WIN32 #include <windows.h> #define PATH_SEP '\\' #elif defined(__linux__) #include <unistd.h> #define PATH_SEP '/' #elif defined(__APPLE__) #include <TargetConditionals.h> #define PATH_SEP '/' #else #error "Unsupported platform" #endif // Feature detection #ifdef __STDC_VERSION__ #if __STDC_VERSION__ >= 201112L // C11 or later #define HAS_C11 1 #endif #endif // Debug builds #ifdef NDEBUG #define debug_print(fmt, ...) ((void)0) #else #define debug_print(fmt, ...) \ fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", \ __FILE__, __LINE__, ##__VA_ARGS__) #endif // Include guards (prevent multiple inclusion) #ifndef MYHEADER_H #define MYHEADER_H // Header contents here #endif // MYHEADER_H // Or use #pragma once (non-standard but widely supported) #pragma once
Predefined Macros
printf("File: %s\n", __FILE__); // Current file name printf("Line: %d\n", __LINE__); // Current line number printf("Date: %s\n", __DATE__); // Compilation date printf("Time: %s\n", __TIME__); // Compilation time printf("Function: %s\n", __func__); // Current function (C99+) // Standard macros #if defined(__STDC__) printf("Standard C\n"); #endif #ifdef __STDC_VERSION__ printf("C version: %ld\n", __STDC_VERSION__); // 199901L for C99 // 201112L for C11 // 201710L for C17/C18 #endif
Header Files and Compilation
Header File Structure
// point.h #ifndef POINT_H #define POINT_H // Type definitions typedef struct { double x; double y; } Point; // Function declarations (prototypes) Point point_create(double x, double y); double point_distance(const Point *p1, const Point *p2); void point_print(const Point *p); // Inline functions (C99+) static inline Point point_add(Point p1, Point p2) { return (Point){p1.x + p2.x, p1.y + p2.y}; } #endif // POINT_H
Implementation File
// point.c #include "point.h" #include <stdio.h> #include <math.h> Point point_create(double x, double y) { Point p = {x, y}; return p; } double point_distance(const Point *p1, const Point *p2) { double dx = p2->x - p1->x; double dy = p2->y - p1->y; return sqrt(dx*dx + dy*dy); } void point_print(const Point *p) { printf("Point(%.2f, %.2f)\n", p->x, p->y); }
Compilation Process
# 1. Preprocessing: Expand macros, include headers gcc -E main.c -o main.i # 2. Compilation: Generate assembly gcc -S main.c -o main.s # 3. Assembly: Generate object code gcc -c main.c -o main.o # 4. Linking: Combine object files and libraries gcc main.o point.o -o program -lm # All in one step gcc -Wall -Wextra -std=c11 -o program main.c point.c -lm # Common flags # -Wall -Wextra Enable most warnings # -Werror Treat warnings as errors # -std=c11 Use C11 standard # -O2 Optimization level 2 # -g Include debug symbols # -D DEFINE_NAME=val Define preprocessor macro # -I /path/to/include Add include directory # -L /path/to/libs Add library directory # -l library Link with library
Static vs Dynamic Linking
# Static library (.a) # Create object files gcc -c module1.c module2.c # Create static library ar rcs libmylib.a module1.o module2.o # Link with static library gcc main.c -L. -lmylib -o program # Dynamic library (.so on Linux, .dylib on macOS, .dll on Windows) # Create position-independent code gcc -c -fPIC module1.c module2.c # Create shared library gcc -shared -o libmylib.so module1.o module2.o # Link with dynamic library gcc main.c -L. -lmylib -o program # Run with dynamic library (if not in standard path) LD_LIBRARY_PATH=. ./program
Common Idioms and Patterns
Error Handling
// Return codes #define SUCCESS 0 #define ERROR_NULL_POINTER -1 #define ERROR_OUT_OF_MEMORY -2 #define ERROR_INVALID_INPUT -3 int process_data(const char *input, char **output) { if (input == NULL || output == NULL) { return ERROR_NULL_POINTER; } *output = malloc(100); if (*output == NULL) { return ERROR_OUT_OF_MEMORY; } // Process data... return SUCCESS; } // Usage with goto for cleanup int function(void) { char *buffer = NULL; FILE *file = NULL; int result = SUCCESS; buffer = malloc(1024); if (buffer == NULL) { result = ERROR_OUT_OF_MEMORY; goto cleanup; } file = fopen("data.txt", "r"); if (file == NULL) { result = -1; goto cleanup; } // Process file... cleanup: free(buffer); if (file != NULL) { fclose(file); } return result; }
Opaque Pointers (Information Hiding)
// header.h typedef struct Widget Widget; // Forward declaration Widget* widget_create(void); void widget_destroy(Widget *w); void widget_set_value(Widget *w, int value); int widget_get_value(const Widget *w); // implementation.c struct Widget { // Full definition hidden int value; char name[50]; // Internal implementation details }; Widget* widget_create(void) { Widget *w = malloc(sizeof(Widget)); if (w != NULL) { w->value = 0; strcpy(w->name, "default"); } return w; } void widget_destroy(Widget *w) { free(w); }
RAII-like Pattern with Cleanup Attribute (GCC/Clang)
// GCC/Clang cleanup attribute #define AUTO_FREE __attribute__((cleanup(cleanup_free))) static inline void cleanup_free(void *p) { free(*(void**)p); } void example(void) { AUTO_FREE char *buffer = malloc(100); if (buffer == NULL) { return; // No memory leak } // Use buffer... // Automatically freed when out of scope }
Generic Programming with Macros
// Type-generic min/max (C11+) #define min(a, b) _Generic((a), \ int: min_int, \ double: min_double, \ default: min_int \ )(a, b) static inline int min_int(int a, int b) { return a < b ? a : b; } static inline double min_double(double a, double b) { return a < b ? a : b; } // Simpler macro approach (works with any type) #define MIN(a, b) ({ \ __typeof__(a) _a = (a); \ __typeof__(b) _b = (b); \ _a < _b ? _a : _b; \ })
Container_of Pattern (Linux Kernel Style)
#define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member))) typedef struct { int id; char name[50]; } Data; typedef struct Node { struct Node *next; Data data; } Node; // Get Node from Data pointer Data *data_ptr = &node->data; Node *node_ptr = container_of(data_ptr, Node, data);
Safety Patterns
Buffer Overflow Prevention
// BAD: Unsafe char buffer[10]; strcpy(buffer, user_input); // Buffer overflow if input > 9 chars // GOOD: Safe with bounds checking char buffer[10]; strncpy(buffer, user_input, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; // Ensure null termination // BETTER: Use safer functions (C11) #ifdef __STDC_LIB_EXT1__ strcpy_s(buffer, sizeof(buffer), user_input); #endif // BEST: Dynamic allocation based on input size size_t len = strlen(user_input) + 1; char *buffer = malloc(len); if (buffer != NULL) { strcpy(buffer, user_input); // ... free(buffer); }
NULL Pointer Checks
// Always check after allocation int *data = malloc(n * sizeof(int)); if (data == NULL) { fprintf(stderr, "Allocation failed\n"); return -1; } // Check function parameters void process(const int *data, size_t n) { if (data == NULL) { return; // Or handle error } // Safe to use data } // Check before dereferencing if (ptr != NULL && ptr->field > 0) { // Safe access }
Integer Overflow Prevention
#include <stdint.h> #include <limits.h> // Check before multiplication size_t safe_multiply(size_t a, size_t b) { if (a > 0 && b > SIZE_MAX / a) { return 0; // Overflow would occur } return a * b; } // Check before addition int safe_add(int a, int b) { if (a > 0 && b > INT_MAX - a) { return INT_MAX; // Saturate } if (a < 0 && b < INT_MIN - a) { return INT_MIN; // Saturate } return a + b; } // Use wider types for intermediate calculations uint32_t a = 1000000; uint32_t b = 1000000; uint64_t result = (uint64_t)a * (uint64_t)b;
Memory Leak Prevention
// Pattern: Always pair malloc with free void example(void) { char *buffer = malloc(100); if (buffer == NULL) { return; } // Use buffer... free(buffer); } // Pattern: Set pointer to NULL after free free(ptr); ptr = NULL; // Pattern: Use goto for cleanup in complex functions int complex_function(void) { void *res1 = NULL, *res2 = NULL, *res3 = NULL; int status = -1; res1 = malloc(100); if (res1 == NULL) goto cleanup; res2 = malloc(200); if (res2 == NULL) goto cleanup; res3 = malloc(300); if (res3 == NULL) goto cleanup; // Do work... status = 0; cleanup: free(res3); free(res2); free(res1); return status; }
Troubleshooting
Segmentation Fault
Causes:
- Dereferencing NULL pointer
- Dereferencing uninitialized pointer
- Writing beyond array bounds
- Use-after-free
Debugging:
# Compile with debug symbols gcc -g program.c -o program # Run with debugger gdb ./program (gdb) run # When crash occurs: (gdb) backtrace (gdb) print variable_name # Use valgrind for memory errors valgrind --leak-check=full ./program
Memory Leaks
// LEAK: malloc without free void leak_example(void) { char *buffer = malloc(100); // ... use buffer ... // Missing: free(buffer); } // LEAK: Losing pointer to allocated memory void lose_pointer(void) { char *buffer = malloc(100); buffer = malloc(200); // Lost first allocation! free(buffer); // Only frees second allocation } // FIX: Track all allocations void correct_version(void) { char *buffer = malloc(100); // ... use buffer ... free(buffer); }
Undefined Behavior
// Uninitialized variable int x; printf("%d\n", x); // UB: undefined value // Fix: Initialize int x = 0; // Modifying string literal char *str = "Hello"; str[0] = 'h'; // UB: segfault on many systems // Fix: Use array char str[] = "Hello"; str[0] = 'h'; // OK // Signed integer overflow int x = INT_MAX; x++; // UB // Fix: Check before operation or use unsigned if (x < INT_MAX) { x++; }
Compiler Warnings
// Enable all warnings gcc -Wall -Wextra -Wpedantic program.c // Common warnings: // - Unused variable: Remove or cast to void // - Implicit declaration: Include proper header // - Format string mismatch: Match printf format with type // - Comparison between signed/unsigned: Cast appropriately // - Missing return: Add return statement // Example fixes: void example(void) { int unused = 5; (void)unused; // Suppress warning // Correct format specifiers size_t sz = 100; printf("%zu\n", sz); // Use %zu for size_t int64_t big = 1000000LL; printf("%" PRId64 "\n", big); // Use PRId64 from inttypes.h }
Concurrency
C has no built-in concurrency primitives in the standard. Most concurrent programming uses POSIX threads (pthreads) or platform-specific APIs.
POSIX Threads (pthreads)
#include <pthread.h> #include <stdio.h> #include <stdlib.h> // Thread function void *thread_function(void *arg) { int *value = (int *)arg; printf("Thread received: %d\n", *value); // Return value int *result = malloc(sizeof(int)); *result = *value * 2; return result; } int main(void) { pthread_t thread; int input = 42; // Create thread if (pthread_create(&thread, NULL, thread_function, &input) != 0) { perror("pthread_create"); return 1; } // Wait for thread to finish void *result; if (pthread_join(thread, &result) != 0) { perror("pthread_join"); return 1; } printf("Result: %d\n", *(int *)result); free(result); return 0; }
Mutexes (Mutual Exclusion)
#include <pthread.h> // Shared data protected by mutex typedef struct { int counter; pthread_mutex_t mutex; } SafeCounter; void counter_init(SafeCounter *c) { c->counter = 0; pthread_mutex_init(&c->mutex, NULL); } void counter_increment(SafeCounter *c) { pthread_mutex_lock(&c->mutex); c->counter++; pthread_mutex_unlock(&c->mutex); } int counter_get(SafeCounter *c) { pthread_mutex_lock(&c->mutex); int value = c->counter; pthread_mutex_unlock(&c->mutex); return value; } void counter_destroy(SafeCounter *c) { pthread_mutex_destroy(&c->mutex); } // Usage void *increment_thread(void *arg) { SafeCounter *counter = (SafeCounter *)arg; for (int i = 0; i < 1000; i++) { counter_increment(counter); } return NULL; }
Condition Variables
#include <pthread.h> typedef struct { int ready; pthread_mutex_t mutex; pthread_cond_t cond; } Signal; void signal_init(Signal *s) { s->ready = 0; pthread_mutex_init(&s->mutex, NULL); pthread_cond_init(&s->cond, NULL); } // Producer: signal when ready void signal_notify(Signal *s) { pthread_mutex_lock(&s->mutex); s->ready = 1; pthread_cond_signal(&s->cond); // Wake one waiter pthread_mutex_unlock(&s->mutex); } // Consumer: wait for signal void signal_wait(Signal *s) { pthread_mutex_lock(&s->mutex); while (!s->ready) { pthread_cond_wait(&s->cond, &s->mutex); } pthread_mutex_unlock(&s->mutex); } void signal_destroy(Signal *s) { pthread_mutex_destroy(&s->mutex); pthread_cond_destroy(&s->cond); }
Atomic Operations (C11+)
#include <stdatomic.h> #include <pthread.h> // Atomic counter (lock-free) typedef struct { atomic_int counter; } AtomicCounter; void atomic_counter_init(AtomicCounter *c) { atomic_init(&c->counter, 0); } void atomic_counter_increment(AtomicCounter *c) { atomic_fetch_add(&c->counter, 1); } int atomic_counter_get(AtomicCounter *c) { return atomic_load(&c->counter); } // Compare and swap _Bool atomic_compare_exchange_example(atomic_int *ptr, int expected, int desired) { return atomic_compare_exchange_strong(ptr, &expected, desired); } // Memory ordering void atomic_with_ordering(void) { atomic_int x, y; // Sequential consistency (default) atomic_store(&x, 1); // Relaxed ordering (weakest) atomic_store_explicit(&y, 2, memory_order_relaxed); // Acquire-release ordering atomic_store_explicit(&x, 1, memory_order_release); atomic_load_explicit(&x, memory_order_acquire); }
Thread-Local Storage
#include <pthread.h> #include <stdio.h> // Thread-local variable (C11+) _Thread_local int thread_id = 0; void *thread_func(void *arg) { thread_id = *(int *)arg; printf("Thread ID: %d\n", thread_id); return NULL; } // POSIX thread-specific data pthread_key_t key; void cleanup(void *data) { free(data); } void create_thread_data(void) { pthread_key_create(&key, cleanup); } void set_thread_data(int value) { int *data = malloc(sizeof(int)); *data = value; pthread_setspecific(key, data); } int get_thread_data(void) { int *data = pthread_getspecific(key); return data ? *data : -1; }
See also:
patterns-concurrency-dev for cross-language concurrency patterns
Serialization
C has no standard serialization framework. Data is typically serialized manually or using third-party libraries.
Manual Binary Serialization
#include <stdio.h> #include <stdint.h> #include <string.h> typedef struct { uint32_t id; char name[50]; float score; } Record; // Serialize to file (binary) int serialize_record(const Record *record, const char *filename) { FILE *file = fopen(filename, "wb"); if (file == NULL) { return -1; } size_t written = fwrite(record, sizeof(Record), 1, file); fclose(file); return written == 1 ? 0 : -1; } // Deserialize from file (binary) int deserialize_record(Record *record, const char *filename) { FILE *file = fopen(filename, "rb"); if (file == NULL) { return -1; } size_t read = fread(record, sizeof(Record), 1, file); fclose(file); return read == 1 ? 0 : -1; } // Network-safe serialization (handle endianness) #include <arpa/inet.h> // For htonl, ntohl typedef struct { uint32_t id; uint32_t value; } NetworkMessage; void serialize_network(const NetworkMessage *msg, uint8_t *buffer) { uint32_t *ptr = (uint32_t *)buffer; ptr[0] = htonl(msg->id); // Host to network byte order ptr[1] = htonl(msg->value); } void deserialize_network(NetworkMessage *msg, const uint8_t *buffer) { const uint32_t *ptr = (const uint32_t *)buffer; msg->id = ntohl(ptr[0]); // Network to host byte order msg->value = ntohl(ptr[1]); }
JSON Serialization (cJSON Library)
// cJSON: https://github.com/DaveGamble/cJSON #include <cJSON.h> typedef struct { char name[50]; int age; char email[100]; } User; // Serialize struct to JSON string char *user_to_json(const User *user) { cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "name", user->name); cJSON_AddNumberToObject(root, "age", user->age); cJSON_AddStringToObject(root, "email", user->email); char *json_str = cJSON_Print(root); // Caller must free cJSON_Delete(root); return json_str; } // Deserialize JSON string to struct int user_from_json(User *user, const char *json_str) { cJSON *root = cJSON_Parse(json_str); if (root == NULL) { return -1; } cJSON *name = cJSON_GetObjectItem(root, "name"); cJSON *age = cJSON_GetObjectItem(root, "age"); cJSON *email = cJSON_GetObjectItem(root, "email"); if (!cJSON_IsString(name) || !cJSON_IsNumber(age) || !cJSON_IsString(email)) { cJSON_Delete(root); return -1; } strncpy(user->name, name->valuestring, sizeof(user->name) - 1); user->age = age->valueint; strncpy(user->email, email->valuestring, sizeof(user->email) - 1); cJSON_Delete(root); return 0; } // Usage void json_example(void) { User user = {"Alice", 30, "alice@example.com"}; // Serialize char *json = user_to_json(&user); printf("%s\n", json); // Deserialize User parsed; if (user_from_json(&parsed, json) == 0) { printf("Parsed: %s, %d, %s\n", parsed.name, parsed.age, parsed.email); } free(json); }
Struct Packing and Alignment
#include <stddef.h> // Default alignment (padding added) struct Unpacked { char a; // 1 byte // 3 bytes padding int b; // 4 bytes char c; // 1 byte // 3 bytes padding }; // Total: 12 bytes // Packed struct (no padding) struct __attribute__((packed)) Packed { char a; // 1 byte int b; // 4 bytes char c; // 1 byte }; // Total: 6 bytes // GCC/Clang: #pragma pack #pragma pack(push, 1) struct PackedPragma { char a; int b; char c; }; #pragma pack(pop) // Check alignment void check_alignment(void) { printf("Unpacked size: %zu\n", sizeof(struct Unpacked)); printf("Packed size: %zu\n", sizeof(struct Packed)); // Get field offset printf("Offset of b: %zu\n", offsetof(struct Unpacked, b)); } // Serialization with packed structs int serialize_packed(const struct Packed *data, const char *filename) { FILE *file = fopen(filename, "wb"); if (file == NULL) { return -1; } // Safe: no padding, predictable layout fwrite(data, sizeof(struct Packed), 1, file); fclose(file); return 0; }
Protocol Buffers / MessagePack (Third-Party)
// Using protobuf-c: https://github.com/protobuf-c/protobuf-c // Define .proto file, generate C code with protoc-c // Example usage (generated code): /* Person person = PERSON__INIT; person.name = "Alice"; person.id = 123; // Serialize size_t len = person__get_packed_size(&person); uint8_t *buffer = malloc(len); person__pack(&person, buffer); // Deserialize Person *parsed = person__unpack(NULL, len, buffer); printf("Name: %s, ID: %d\n", parsed->name, parsed->id); person__free_unpacked(parsed, NULL); */
See also:
patterns-serialization-dev for cross-language serialization patterns
Testing
C has no built-in test framework. Unit testing typically uses third-party frameworks like Unity, CMocka, or Check.
Unity Framework
// Unity: https://github.com/ThrowTheSwitch/Unity #include "unity.h" // Functions under test int add(int a, int b) { return a + b; } int divide(int a, int b) { if (b == 0) return -1; // Error code return a / b; } // Setup/teardown (called before/after each test) void setUp(void) { // Initialize test fixtures } void tearDown(void) { // Clean up after test } // Test cases void test_add_positive_numbers(void) { TEST_ASSERT_EQUAL_INT(5, add(2, 3)); TEST_ASSERT_EQUAL_INT(10, add(7, 3)); } void test_add_negative_numbers(void) { TEST_ASSERT_EQUAL_INT(-5, add(-2, -3)); TEST_ASSERT_EQUAL_INT(0, add(-5, 5)); } void test_divide_success(void) { TEST_ASSERT_EQUAL_INT(2, divide(6, 3)); TEST_ASSERT_EQUAL_INT(-2, divide(-6, 3)); } void test_divide_by_zero(void) { TEST_ASSERT_EQUAL_INT(-1, divide(10, 0)); } // Main test runner int main(void) { UNITY_BEGIN(); RUN_TEST(test_add_positive_numbers); RUN_TEST(test_add_negative_numbers); RUN_TEST(test_divide_success); RUN_TEST(test_divide_by_zero); return UNITY_END(); }
Common Unity Assertions
// Integer assertions TEST_ASSERT_EQUAL_INT(expected, actual); TEST_ASSERT_NOT_EQUAL_INT(expected, actual); TEST_ASSERT_GREATER_THAN_INT(threshold, actual); TEST_ASSERT_LESS_THAN_INT(threshold, actual); TEST_ASSERT_INT_WITHIN(delta, expected, actual); // Floating point TEST_ASSERT_EQUAL_FLOAT(expected, actual); TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual); // Strings TEST_ASSERT_EQUAL_STRING(expected, actual); TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, length); // Memory TEST_ASSERT_EQUAL_MEMORY(expected, actual, length); // Pointers TEST_ASSERT_NULL(pointer); TEST_ASSERT_NOT_NULL(pointer); TEST_ASSERT_EQUAL_PTR(expected, actual); // Boolean TEST_ASSERT_TRUE(condition); TEST_ASSERT_FALSE(condition); // Custom message TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, "Custom failure message");
CMocka Framework
// CMocka: https://cmocka.org/ #include <stdarg.h> #include <stddef.h> #include <setjmp.h> #include <cmocka.h> // Function to test char *format_name(const char *first, const char *last) { if (first == NULL || last == NULL) { return NULL; } size_t len = strlen(first) + strlen(last) + 2; char *result = malloc(len); snprintf(result, len, "%s %s", first, last); return result; } // Test with setup/teardown static int setup(void **state) { char *test_data = strdup("test"); *state = test_data; return 0; } static int teardown(void **state) { free(*state); return 0; } // Test functions static void test_format_name_success(void **state) { (void)state; // Unused char *result = format_name("John", "Doe"); assert_non_null(result); assert_string_equal(result, "John Doe"); free(result); } static void test_format_name_null_input(void **state) { (void)state; char *result = format_name(NULL, "Doe"); assert_null(result); result = format_name("John", NULL); assert_null(result); } // Test runner int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_format_name_success), cmocka_unit_test(test_format_name_null_input), cmocka_unit_test_setup_teardown(test_with_fixture, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); }
Mocking with CMocka
// Mock a function int __wrap_database_get_user(int id); // Our mock int __real_database_get_user(int id); // Real function int __wrap_database_get_user(int id) { check_expected(id); return (int)mock(); } // Test using mock static void test_with_mock(void **state) { (void)state; // Expect call with id=1, return 42 expect_value(__wrap_database_get_user, id, 1); will_return(__wrap_database_get_user, 42); // Function under test calls database_get_user int result = process_user(1); assert_int_equal(result, 42); } // Compile with: -Wl,--wrap=database_get_user
Check Framework
// Check: https://libcheck.github.io/check/ #include <check.h> START_TEST(test_add) { ck_assert_int_eq(add(2, 3), 5); ck_assert_int_eq(add(-1, 1), 0); } END_TEST START_TEST(test_divide) { ck_assert_int_eq(divide(6, 3), 2); ck_assert_int_eq(divide(10, 0), -1); } END_TEST Suite *create_suite(void) { Suite *s = suite_create("Math"); TCase *tc_core = tcase_create("Core"); tcase_add_test(tc_core, test_add); tcase_add_test(tc_core, test_divide); suite_add_tcase(s, tc_core); return s; } int main(void) { Suite *s = create_suite(); SRunner *sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); int failed = srunner_ntests_failed(sr); srunner_free(sr); return (failed == 0) ? 0 : 1; }
Test Organization
// test_math.c #include "unity.h" #include "math_functions.h" void test_addition(void) { TEST_ASSERT_EQUAL_INT(5, add(2, 3)); } void test_subtraction(void) { TEST_ASSERT_EQUAL_INT(2, subtract(5, 3)); } // test_string.c #include "unity.h" #include "string_functions.h" void test_concat(void) { char *result = concat("Hello", "World"); TEST_ASSERT_EQUAL_STRING("HelloWorld", result); free(result); } // test_runner.c #include "unity.h" // External test declarations extern void test_addition(void); extern void test_subtraction(void); extern void test_concat(void); void setUp(void) {} void tearDown(void) {} int main(void) { UNITY_BEGIN(); // Math tests RUN_TEST(test_addition); RUN_TEST(test_subtraction); // String tests RUN_TEST(test_concat); return UNITY_END(); }
Coverage and Profiling
# Compile with coverage flags (GCC/Clang) gcc -fprofile-arcs -ftest-coverage -o test_program test.c functions.c # Run tests ./test_program # Generate coverage report gcov functions.c lcov --capture --directory . --output-file coverage.info genhtml coverage.info --output-directory coverage_html
Cross-Cutting Patterns
For cross-language comparison and translation patterns, see:
- Threads, mutexes, atomics, channelspatterns-concurrency-dev
- JSON, binary formats, endiannesspatterns-serialization-dev
- Macros, code generationpatterns-metaprogramming-dev
References
- C Programming Language (K&R Book)
- C Standard Library Reference
- GCC Documentation
- Clang Documentation
- Specialized skills:
,lang-c-memory-eng
,lang-c-posix-dev
,lang-c-library-dev
,lang-c-systems-englang-c-embedded-dev