Minimalist C Architecture
Context
In an era of increasing software bloat, the C ecosystem has become reliant on heavy build systems and deep dependency trees. This creates 'dependency hell,' slows developer feedback loops, and produces binaries with unnecessary overhead. This project explores a return to fundamental patterns.
Decision
Adopt a 'Header-less' Unity Build architectural pattern: eliminate external library dependencies and separate compilation in favor of X-Macros and single-translation-unit builds.
Alternatives Considered
Standard Modular Compilation (CMake/Meson)
- Widespread industry familiarity
- Incremental builds save time in massive codebases
- Clear interface separation via headers
- Significant build-system maintenance overhead
- Fragile dependency management
- Inhibits global compiler optimizations (LTO required)
Dynamic Library Micro-services
- Independent updates for system components
- Shared memory across different applications
- Runtime linking overhead
- Version mismatch issues ('DLL Hell')
- Increases attack surface for security vulnerabilities
Reasoning
A minimalist architecture reduces the 'cognitive load' of a project. By using X-Macros for state management and Unity Builds for compilation, the entire program logic becomes visible to the compiler at once. This enables aggressive optimization, near-instant build times, and produces tiny, zero-dependency binaries that run on any POSIX system.
The Bloat Problem
Modern systems development often prioritizes developer convenience over machine efficiency. This leads to:
- Binary Bloat: Simple utilities often exceed several megabytes due to static linking of unused library code.
- Slow Feedback: Complex build systems can take seconds or minutes to resolve dependencies before a single line is compiled.
- Fragmentation: Memory management becomes inconsistent when mixing multiple third-party allocation strategies.
Architectural Pillars
We codified three pillars to maintain a professional, scalable codebase without the traditional baggage:
1. The TBL (Table) Macro Pattern
We replaced complex config parsers and global registry arrays with X-Macros. This allows a single definition of a system component—like a keybinding or an error code—to be expanded multiple times for different purposes (e.g., generating an enum, a string array for logging, and a dispatch table).
2. Unity Builds (Single Translation Unit)
Instead of compiling ten .c files into ten .o files and linking them, we include all implementation files into a single main.c.
- Near-Instant Builds: Compiling with
tcc(Tiny C Compiler) takes less than 50ms. - Global Optimization: The compiler performs constant folding and dead-code elimination across the entire project without needing a separate Link-Time Optimization (LTO) pass.
3. Custom Memory Arenas
To bypass the fragmentation of malloc, we use linear memory arenas. This allows for deterministic allocation and “frame-based” deallocation, which is significantly faster and easier to debug than tracking individual pointers.
Results & Impact
- Compilation Speed: Build times dropped from ~2 seconds (Make/GCC) to < 50ms (TCC), enabling a ‘save-to-test’ loop that feels instantaneous.
- Binary Footprint: Core system utilities (like FocusWM) were reduced to under 50KB, fitting entirely within the CPU’s L1 cache for maximum execution speed.
- Portability: Binaries are statically linked against a minimal libc, allowing them to run on any Linux kernel version without requiring specific shared libraries.
- Maintainability: The entire architecture is contained within a few hundred lines of code, making it possible for a single developer to hold the entire system state in their head.
The Road Ahead
The primary challenge remains standardization. While this architecture is perfect for solo power-users and high-performance system tools, it requires a shift in mindset for collaborative environments. My next goal is to document a “Minimalist C Standard Library” to provide a common ground for developers adopting this pattern.