Safe c.h
lastmod 2025-11-20created 2025-11-201 edits
Giving C a superpower - a custom header file (safe_c.h)
About
- safe_c.h is a 600-line custom header file that adds C++ and Rust safety and convenience features to the C language; it was used to implement a memory-leak-free, thread-safe grep (cgrep)
- Automates resource management without manual free() calls through RAII, smart pointers, and the automatic cleanup attribute
- Handles buffer overflows, error handling, and precondition checks safely with vectors, views, a Result type, contract macros, and more
- Ensures safety while keeping concurrency and performance, with automatic mutex unlocking, thread-spawn macros, branch prediction optimization, and more
- As a result, demonstrates that leak-free, segfault-free C code can be written with the same performance (-O2 level)
safe_c.h overview
- safe_c.h is a header file that ports C++ and Rust features to C code
- Provides the same RAII (automatic cleanup) behavior even on compilers that do not support C23's cleanup attribute (GCC 11, Clang 18, etc.)
- The CLEANUP(func) macro automatically releases resources when a function exits
- The LIKELY() and UNLIKELY() macros optimize branch prediction on hot paths
Memory management: UniquePtr and SharedPtr
- UniquePtr is a single-ownership smart pointer that automatically calls free() at the end of its scope
- When declared with the AUTO_UNIQUE_PTR() macro, memory is freed automatically even on errors or early returns
- shared_ptr_init() and shared_ptr_copy() handle reference increments and decrements automatically
- Used to manage structs safely shared between threads
Preventing buffer overflows: Vector and View
- The DEFINE_VECTOR_TYPE() macro creates type-safe, auto-growing vectors
- Handles reallocation, capacity management, and cleanup automatically
- When declared with AUTO_TYPED_VECTOR(), it is freed automatically at the end of its scope
- DEFINE_SPAN_TYPE() defines a Span per type
- Includes bounds checking to guarantee safe array access
Error handling: Result type and RAII
- The Result struct is a return type that distinguishes success/failure, similar to Rust's Result
- DEFINE_RESULT_TYPE() creates a result structure per type
- RESULT_IS_OK() and RESULT_UNWRAP_ERROR() give clear error handling
- The AUTO_MEMORY() macro automatically cleans up malloc'd memory
Contracts and safe strings
- The requires() / ensures() macros state a function's pre- and postconditions
- Prints a clear error message on failure
- Returns false on failure for safe error handling
Concurrency: automatic unlocking and thread macros
- A CLEANUP-based automatic mutex unlock function prevents deadlocks
- Automatically calls pthread_mutex_unlock() at the end of the scope
- Used to implement cgrep's file-processing thread pool
Performance optimization
- The LIKELY() / UNLIKELY() macros provide branch prediction for hot paths
- Achieves PGO-level optimization effects even in -O2 builds
Conclusion
- cgrep, built with safe_c.h, is 2,300 lines of C code and eliminated more than 50 manual free() calls
- Implements safe C code without memory leaks or segfaults while keeping the same assembly and execution speed
- An example of combining modern safety with C's simplicity and freedom
- The author plans to cover in a later post why cgrep is more than 2x faster than ripgrep and uses 20x less memory
- safe_c.h suits new projects; the author notes that being macro-based may make debugging harder
- Correctness and safety verified with various static analyzers (GCC analyzer, ASAN, UBSAN, Clang-tidy, etc.)