Skip to content
Search

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
  • SharedPtr is an automatic reference-counting structure that destroys the resource automatically when the last reference is released
    • 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
  • StringView and Span are non-owning reference structs that handle string and array slices without a separate malloc
    • 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
  • Combined with the CLEANUP attribute, resources are released automatically when a function exits
    • 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
  • safe_strcpy() is a copy function with buffer size checks that prevents overflows
    • 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
  • The SPAWN_THREAD() and JOIN_THREAD() macros simplify creating and joining threads
    • 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
  • No performance loss even with the added safety features

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.)

See also

Favorite site

author Zer0 sitemap