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.
- 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 onmallocfailure. - 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
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.
| 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). |
-
Prerequisites: CMake (3.15+), Make (3.81+), and a C Compiler (GCC, Clang, or MSVC).
-
Build:
- Developer Build (Debug symbols, Sanitizers ON, Warnings as Errors)
make dev
- Release Build (Optimization -O3, Sanitizers OFF)
make release
- Developer Build (Debug symbols, Sanitizers ON, Warnings as Errors)
-
Run Tests:
make test -
Generate Doxygen Documentation:
make docs # To clean docs, run: make clean_docs
#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;
}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 */
}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.
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.
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:
- 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.
- You MUST free your data before freeing the container.
- 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.
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.
- Add Code: Put
.hfiles ininclude/and.cfiles insrc/. - Add Tests: Create
tests/test_my_struct.c. CMake detects it automatically.