Skip to content

Coding Standards

Language & Compiler

Item Standard
Language C11 (-std=c11)
Compiler GCC ≥ 9.0
Warnings -Wall -Wextra -Werror (zero warnings policy)
Optimization -O3 for production, -O0 -g for debug
Sanitizers -fsanitize=address,undefined during development

Formatting

Indentation & Braces

/* K&R style braces, 4-space indentation */
static inline int function_name(int param1, const char *param2)
{
    if (param1 < 0) {
        return -EINVAL;
    }

    for (int i = 0; i < param1; i++) {
        do_something(i);
    }

    return 0;
}

Naming Conventions

Element Convention Example
Functions module_verb_noun raw_socket_open, ring_buffer_push
Types (struct) module_noun_t raw_socket_t, uart_config_t
Types (enum) module_noun_t uart_parity_t, gpio_edge_t
Enum values MODULE_UPPER_CASE UART_PARITY_NONE, GPIO_EDGE_RISING
Constants MODULE_UPPER_CASE REACTOR_MAX_EVENTS, ETH_HDR_SIZE
Local variables snake_case frame_len, slot_ptr
Struct members snake_case if_index, baud_rate

Line Length

  • Maximum 90 characters per line
  • Break long function calls at parameter boundaries:
int rc = raw_socket_build_frame(buf, dst_mac, src_mac,
                                ethertype, payload, payload_len);

Design Patterns

Header-Only Implementation

All protocol modules use static inline functions in the .h file:

/* Good: zero-cost abstraction, inlineable */
static inline int module_operation(module_t *ctx, int param)
{
    /* Implementation */
    return 0;
}

Rationale: Eliminates function call overhead in the hot path. The compiler can inline and optimize across module boundaries.

Error Handling

Return -errno on failure (never positive error codes):

static inline int uart_open(uart_t *uart, const char *device,
                            const uart_config_t *config)
{
    uart->fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (uart->fd < 0) {
        return -errno;  /* ← return negative errno */
    }
    return 0;           /* ← success = 0 */
}

Caller pattern:

int rc = uart_open(&uart, "/dev/ttyS0", &cfg);
if (rc < 0) {
    fprintf(stderr, "Failed: %s\n", strerror(-rc));
    return rc;
}

Zero Dynamic Allocation

All memory must be pre-allocated by the caller:

/* Good: caller provides storage */
static uint8_t ring_storage[4096 * 64] __attribute__((aligned(64)));
ring_buffer_init(&rb, ring_storage, 4096, 64);

/* Bad: internal malloc */
rb->buffer = malloc(capacity * slot_size);  /* NEVER do this */

Resource Cleanup

Every _open() has a matching _close(). On failure during open, clean up partial state:

static inline int module_open(module_t *m)
{
    m->fd = open(...);
    if (m->fd < 0) return -errno;

    if (ioctl(m->fd, ...) < 0) {
        int err = errno;
        close(m->fd);      /* ← clean up partial state */
        return -err;
    }

    return 0;
}

Packed Structures for Wire Formats

Use __attribute__((packed)) for protocol frame structures:

typedef struct __attribute__((packed)) {
    uint8_t  dst_mac[6];
    uint8_t  src_mac[6];
    uint16_t ethertype;
    uint8_t  payload[];
} eth_frame_t;

Atomic Operations

Use C11 <stdatomic.h> with explicit memory ordering:

/* Never use default (sequential consistency) in hot paths */
/* Bad:  */ atomic_store(&head, value);
/* Good: */ atomic_store_explicit(&head, value, memory_order_release);

Documentation Requirements

Header File Documentation

Every .h file must include:

  1. Module name and purpose Single-line description
  2. Protocol specification Wire format, timing, state machine
  3. ASCII diagram Memory layout, frame structure, or architecture
  4. Compilation command Exact GCC invocation
  5. Runtime prerequisites Permissions, kernel modules, hardware

Function Documentation

/**
 * Brief one-line description.
 *
 * Longer description if the function has non-obvious behavior,
 * side effects, or thread-safety considerations.
 *
 * @param name   Description of parameter
 * @param other  Description with constraints (e.g., "must be power of 2")
 * @return       What the return value means (success/failure codes)
 */
static inline int module_function(type *name, type other);

Concurrency Rules

  1. Single-threaded modules (reactor, raw_socket): No atomics needed. Document "NOT thread-safe" in header.
  2. SPSC modules (ring_buffer, spsc_queue): Use acquire/release ordering. No locks.
  3. MPMC modules (memory_pool): Use CAS with ABA prevention. Document retry semantics.
  4. Never use mutexes in the data path. Use lockless structures or partition work per-thread.

Cache Alignment

/* Align hot atomic variables to cache-line boundaries */
alignas(64) _Atomic uint64_t head;
uint8_t _padding[64 - sizeof(_Atomic uint64_t)];

/* Align buffers for DMA and vectorization */
static uint8_t buffer[SIZE] __attribute__((aligned(64)));

Build Flags Reference

Flag Purpose
-Wall -Wextra -Werror Zero tolerance for warnings
-O3 Full optimization (inlining, vectorization)
-std=c11 C11 standard (atomics, alignas)
-march=native CPU-specific optimizations (for benchmarks)
-flto Link-time optimization (cross-TU inlining)
-fno-strict-aliasing Safe for type-punning (wire protocol parsing)
-fsanitize=address ASan for development builds
-fsanitize=thread TSan for concurrency debugging
-DNDEBUG Disable assertions in release

Makefile Template

CC       = gcc
CFLAGS   = -Wall -Wextra -Werror -O3 -std=c11
LDFLAGS  = -lpthread
TARGET   = module_demo
SOURCES  = module.c

.PHONY: all clean

all: $(TARGET)

$(TARGET): $(SOURCES)
    $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

clean:
    rm -f $(TARGET)