Skip to content

hnthap/dsa-vault

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

C Data Structures & Algorithms Vault

A collection of Strict C90, memory-safe, and defensive data structure implementations for modern applications.

Designed for high portability, zero undefined behavior, and rigorous error checking.

🛡️ Project Philosophy

  • Strict C90 (ANSI C): Compiles on virtually any system (Embedded, Legacy, Modern). External identifiers assume a linker capable of distinguishing >6 characters (common in all modern toolchains).
  • Defensive Coding: Every function validates inputs and bounds.
  • Zero Allocator Panic: Returns error codes (-1) instead of crashing on malloc failure.
  • Standalone: Every pair of header and source files is standalone and dependency-free, meaning it ought to be usable even when copied and pasted to any C90-compliant codebases.

DSA Vault provides defensive C data structures designed for reliability in production systems. We prioritize correctness and explicit error handling over convenience and performance. While our design philosophy draws from safety-critical programming practices, this library is intended for general use in infrastructure, systems programming, and applications where robustness matters.

This library is designed for server applications that must handle failures gracefully, embedded systems where crashes are unacceptable, infrastructure code with long operational lifetimes, and systems that process untrusted input.

Our design philosophy rejects hidden control flow, panic-on-error patterns, and assumptions about valid inputs. Every function call will either succeed and modify state, or fail and leave state unchanged. There are no partial failures, no exceptions, and no undefined behavior from API misuse.

Read our project roadmap → TODO.md

⚠️ Know What You're Getting

This library trades convenience for safety:

  • Every operation requires explicit error checking
  • The API is verbose compared to typical C libraries
  • Type safety is your responsibility
  • Performance is good but not optimal (defensive checks add overhead)

If you need:

  • Rapid prototyping with minimal boilerplate → Try a higher-level language
  • Maximum performance at any cost → Consider specialized libraries
  • Type-safe generics → Use C++ templates or Rust
  • Convenience over correctness → This isn't the right fit

We'd rather have 100 users who value our trade-offs than 10,000 who fight against them. You probably want a different library if you are building applications where occasional crashes are acceptable.

📂 Modules

Module Header Description Complexity
Vector dsav_vec.h Dynamic array storing void *. Auto-resizing. Push: O(1) amortized; Access: O(1); Insert/Remove: O(N).

🚀 Setup & Build

  1. Prerequisites: CMake (3.15+), Make (3.81+), and a C Compiler (GCC, Clang, or MSVC).

  2. Build:

    • Developer Build (Debug symbols, Sanitizers ON, Warnings as Errors)
      make dev
    • Release Build (Optimization -O3, Sanitizers OFF)
      make release
  3. Run Tests:

    make test
  4. Generate Doxygen Documentation:

    make docs
    # To clean docs, run: make clean_docs

🛠️ Usage Example

Vector

#include <stdio.h>
#include "dsav_vec.h"

int main(void) {
    dsav_vec tasks;
    char *data = "Task 1";
    void *item;
    
    if (dsav_vec_init(&tasks) != 0) {
        return 1;
    }

    /* Store pointers (Ownership stays with caller)
       If you malloc'd the data, you must free it BEFORE freeing the 
       vector. */
    dsav_vec_push(&tasks, data);
    
    /* Access */
    if (dsav_vec_at(&tasks, 0, &item) == 0) {
        printf("Item: %s\n", (char*)item);
    }

    dsav_vec_free(&tasks);
    return 0;
}

🧠 The Design Choice: Safety Before Convenience

Why Uses Return Codes Everywhere?

We reject exceptions and panic. In safety-critical code, control flow MUST be explicit.

/* ❌ Bad: Hidden control flow */
/* What if index is invalid? Crash? Return NULL? */
void *item = vec_get(&v, 0);

/* ✅ Good: Explicit error handling */
void *item;
if (dsav_vec_at(&v, 0, &item) != 0) {
    /* You MUST decide what to do on error */
}

Why No Convenience Wrappers?

Convenience is a liability. Every "helpful" feature creates ways to ignore errors.

/* ❌ We do not provide this */
/* Returns NULL on error - too generic and non-descriptive */
void *dsav_vec_get(dsav_vec *v, size_t i);

/* ✅ You should use this */
/* Forces explicit check using meaning return code */
int dsav_vec_at(const dsav_vec *v, size_t i, void **p_item);

If this feels painful, that is a signal you're handling errors.

Why Validates Every Operation?

Assume nothing. If a user passes an input that could be corrupted, we MUST assume it's corrupted and prove that it is not, instead of silently compromising.

Memory Ownership: You Own Your Data

The container stores pointers, not data. When you push a pointer into the container, the container copies that pointer value, but your data remains yours to manage.

This means three critical rules:

  1. The validity of the data you put in the container is your responsibility; the container does no operations on the data itself, but only receives it, moves it around, performs simple calculations (such as comparisons) and returns to you.
  2. You MUST free your data before freeing the container.
  3. If you remove an item the container returns the pointer to you - freeing it is your responsibility. Not freeing your data could lead to memory leak.

Correct Pattern:

dsav_vec vec;
char *data = malloc(100);  /* Your data */

dsav_vec_init(&vec);
dsav_vec_push(&vec, data); /* The vector just stores the pointer value */

/* Doing your work... */

/* When cleaning up: data first, then vector */
while (vec->count > 0) {
    void *temp = NULL;
    /* Remove the last element of the vector,
       then store it to temp */
    dsav_vec_pop(&vec, &temp);
    /* Free your data, which you allocated earlier */
    free(temp);
}
/* At the end, free the vector */
dsav_vec_free(&vec);

Why? Generic containers in C have only two options: store copies (requiring you to specify item size) or store pointers. We chose pointers because it allows the same vector to store heterogeneous types, gives you control over allocation strategy, and avoids hidden copying costs.

🔑 Security Considerations

This library implements defensive programming practices that prevent many common security vulnerabilities, including bounds checking to prevent buffer overflows, integer overflow checking to prevent allocation size vulnerability, and careful error handling to prevent use-after-free bugs. However, users should be aware of the following security limitations:

  • Pointer storage: Containers store raw pointers, which are memory addresses. An attacker who can read vector contents through a memory disclosure vulnerability will learn address space layout, defeating ASLR protection.
  • Memory reuse: Freed memory is not cleared. Pointer values remain in memory after vector operations until that memory is reallocated for another purpose.
  • Concurrent modification: If multiple parts of the program access the same vector, the library cannot prevent time-of-check-time-of-use vulnerabilities. Users must ensure exclusive access during operations that need atomicity.
  • Type confusion: Because containers store void pointers, type safety is the caller's responsibility. In correct casts can lead to type confusion vulnerabilities where data is interpreted with the wrong type.

🤝 Contributing / Extending

  1. Add Code: Put .h files in include/ and .c files in src/.
  2. Add Tests: Create tests/test_my_struct.c. CMake detects it automatically.

About

Strict C90, memory-safe, and defensive data structure and algorithm implementations for modern applications.

Resources

License

Stars

Watchers

Forks

Packages