
This project reimplements a large slice of the C++ Standard Library (containers,
algorithms, smart pointers, …) together with many Python-style conveniences
(statistics, random, secrets, json, regex, …) in pure C17. The goal is
to give C developers familiar, well-documented building blocks — dynamic arrays,
maps, strings, JSON, networking, big integers, and much more — without leaving the C
ecosystem.
Zero memory leaks. Every module, test suite, and README example is verified
under Valgrind (--leak-check=full) — 0 leaks, 0 errors. Network/socket code is
additionally checked for descriptor leaks (--track-fds=yes).
Cross-platform. Builds and runs on Windows (MSVC and MinGW-w64) and
Linux (GCC/Clang), with POSIX/Win32 backends behind one API. Compiles cleanly
under -Wall -Wextra.
Pure C17. No C++ — just portable standard C with thin platform shims.
40+ modules. Containers, algorithms, smart pointers, strings, JSON/XML/CSV/INI, networking (TCP/UDP/HTTP), crypto & JWT, arbitrary-precision math, graphics, and more.
Familiar APIs. Modeled on the C++ STL (vector, map, unique_ptr, …) and the
Python standard library (random, statistics, json, regex, turtle, …).
Heavily tested with true results. Deep per-module test suites, plus runnable examples in every README whose shown output is captured from a real run.
Fast & predictable. Efficient data structures (geometric vector growth
with inlined push_back/at, O(1) average hashmap, O(log n) map), clear
ownership rules and deallocators.
CMake build. Build the whole library, a single module, or one example at a time, with GCC, Clang, or MSVC.
The library aims to be fast where it counts. The table below pits each C
container against its C++ <stl> counterpart on the same workload, the same
data, and the same compiler family at -O2. Both programs walk an identical
deterministic key stream and compute identical checksums, so they do exactly the
same work — only the container implementation differs.
Environment: Windows 10, MinGW-w64 GCC / G++ 14.2.0, -O2, single thread,
best of three runs (lower is better). Absolute numbers vary by machine; the
ratio is what carries over.
| C container ↔ C++ STL | Workload (N) | C STL | C++ STL | C ÷ C++ |
|---|---|---|---|---|
vector ↔ std::vector<int> |
push_back + sum, 10,000,000 |
0.041 s | 0.031 s | 1.3× |
map ↔ std::map<int,int> (ordered / RB-tree) |
insert + lookup, 1,000,000 | 1.00 s | 1.20 s | 0.83× |
hashmap ↔ std::unordered_map<int,int> |
insert + lookup, 1,000,000 | 0.231 s | 0.364 s | 0.63× |
set ↔ std::set<int> (ordered / RB-tree) |
insert + lookup, 1,000,000 | 1.17 s | 1.05 s | 1.1× |
priority_queue ↔ std::priority_queue<int> |
push + pop (heap-sort), 1,000,000 | 0.156 s | 0.108 s | 1.4× |
deque ↔ std::deque<int> |
push_back + iterate + pop_front, 1,000,000 |
0.009 s | 0.004 s | 2.2× |
list ↔ std::list<int> |
push_back + iterate + teardown, 1,000,000 |
0.093 s | 0.075 s | 1.2× |
forward_list ↔ std::forward_list<int> |
push_front + iterate + teardown, 1,000,000 |
0.080 s | 0.076 s | 1.05× |
queue ↔ std::queue<int> |
enqueue + dequeue (FIFO), 1,000,000 | 0.006 s | 0.003 s | 2.0× |
stack ↔ std::stack<int> |
push + pop (LIFO), 1,000,000 | 0.005 s | 0.003 s | 1.7× |
C ÷ C++ is the C time divided by the C++ time — below 1.0 means the C
library is faster.
map and hashmap. Both allocate their tree nodes /
buckets from a pooled slab allocator, so a million inserts cost a handful of
big mallocs instead of a million tiny ones, beating the STL's per-node new.set — near parity. Same pooled-slab, inline-element RB-tree as
map. Lookups are actually a hair faster than std::set; inserts are a
little slower, so it nets ~1.1×. Unlike map (which stores caller-owned
pointers and skips the copy), set has value semantics and deep-copies each
element exactly like std::set, so it can't win the copy back — and its node
carries the publicly-inspectable key/left/right/parent/color fields
(the test suite walks them to check the red-black invariants), which a templated
std::set node doesn't expose. A large RB-tree is cache-miss-bound, so that
slightly larger node is the whole gap.vector and priority_queue. Both are tight loops over
raw ints. The C vector now inlines push_back/at at the call site just
like std::vector and closes most of the gap (~1.3×); the small residual is the
type-erased ABI — a runtime itemSize stride and a size-dispatched element copy
where C++ has a compile-time sizeof(int), plus the bounds check in vector_at
that operator[] skips. The C priority_queue size-dispatches its heap moves
the same way (~1.4×); its residual is the comparator — one indirect call
through a function pointer per comparison, which std::priority_queue inlines
from its template argument. Either way it's a small fixed per-element cost — the
price of a uniform C API — not a worse complexity.deque. The C deque stores elements inline in byte-budgeted
block buffers (one allocation per block, not one malloc per element) and
inlines front / back / at with shift-indexed block math like
std::deque, so it lands at ~2.2×; the residual is the out-of-line
push_back / pop_front calls that std::deque inlines from templates.list. Each node stores its element inline in the same allocation
(one malloc per node, like std::list) instead of a second heap block per
element, which roughly halves the allocation/teardown cost and lands it at
~1.2×. The residual is a slightly larger node — the type-erased API keeps a
value pointer and aligns the inline element for any type — plus the
out-of-line per-node call std::list inlines.forward_list — at parity. It already uses the same single-node-
allocation, inline-element design as std::forward_list, so push_front /
iterate / teardown match it within measurement noise (≈1.05×). No type-erasure
penalty survives here because the per-node malloc dominates and both pay it
once per element.queue. The C queue is a contiguous vector used as a ring:
enqueue appends and dequeue advances a front index (an O(1) bump, not a
buffer shift), with the dead front prefix reclaimed in bulk when the buffer
fills — so both ends are amortized O(1), like std::queue. The hot ops are
inlined; the ~2× residual is the per-element copy into the contiguous buffer
vs std::queue's deque node writes. (Previously dequeue shifted the whole
buffer — O(n), an O(n²) drain; that's fixed.)stack. LIFO at the back of a contiguous vector, so push and pop
are already O(1). push / top / pop / empty are now inlined in the header
(the way std::stack inlines through its container), which cut it from ~3.3× to
~1.7×; the residual is the vector's amortized realloc-copy on growth vs
std::stack's default deque adding blocks — in exchange the C stack keeps its
elements fully contiguous.Reproduce it yourself — a C and a C++ program over the identical key stream:
# C (link the containers used + algorithm, which queue depends on):
gcc -O2 bench.c vector/vector.c queue/queue.c priority_queue/priority_queue.c \
hashmap/hashmap.c map/map.c algorithm/algorithm.c -I. -o bench_c && ./bench_c
# C++:
g++ -O2 -std=c++17 bench.cpp -o bench_cpp && ./bench_cpp
I undertake this project out of a deep affection for the C programming language. C remains an essential tool for any computer engineer, providing the foundation needed to build efficient and robust software. This effort aims to enrich the language with the conveniences found in higher-level standard libraries.
Every module lives in its own directory with a .c source, a .h header, and a
README.md containing a full API reference and runnable examples.
| Module | Analogous to | Description |
|---|---|---|
array |
std::array |
Fixed-size array wrapper with bounds-checked access. |
vector |
std::vector |
Dynamic, automatically resizing array with memory pooling. |
string |
std::string |
Growable string with a rich manipulation API (string/std_string.h). |
list |
std::list |
Doubly linked list. |
forward_list |
std::forward_list |
Singly linked list. |
deque |
std::deque |
Double-ended queue with fast insertion/removal at both ends. |
queue |
std::queue |
FIFO queue. |
stack |
std::stack |
LIFO stack. |
priority_queue |
std::priority_queue |
Heap-backed priority queue with a custom comparator. |
span |
std::span |
Non-owning view over a contiguous sequence. |
bitset |
std::bitset |
Fixed-size sequence of bits with bitwise operations. |
map |
std::map |
Ordered associative container (Red-Black tree, O(log n)). |
hashmap |
std::unordered_map |
Hash table with O(1) average insert/lookup and automatic rehashing. |
set |
std::set |
Ordered, unique associative container (Red-Black tree, O(log n)); |
tuple |
std::tuple |
Fixed-size heterogeneous collection. |
variant |
std::variant |
Type-safe tagged union with a visitor interface. |
uniqueptr |
std::unique_ptr |
RAII smart pointer with automatic, scope-based cleanup. |
| Module | Description |
|---|---|
algorithm |
Generic algorithms inspired by <algorithm>: sort, search, transform, accumulate, … |
sort |
Stand-alone sorting library: 8+ algorithms, benchmarking and search helpers. |
statistics |
Mean, median, variance, … (mirrors Python's statistics). |
random |
Pseudo-random numbers and sequence helpers (mirrors Python's random). |
secrets |
Cryptographically secure random numbers/tokens and constant-time comparison. |
uuid |
RFC 9562 Universally Unique Identifiers (v4/v7/v3/v5, Nil/Max), mirroring Python's uuid. |
numbers |
Mathematical constants (header-only, like C++20 <numbers>). |
matrix |
Matrix creation, manipulation and linear-algebra operations. |
bigint |
Arbitrary-precision integers (backed by GMP). |
bigfloat |
Arbitrary-precision floating point (backed by MPFR). |
evalexpr |
Runtime arithmetic-expression evaluator. |
| Module | Description |
|---|---|
fmt |
Formatting / I/O library inspired by Go's fmt, with Unicode support. |
encoding |
Base64 / Base32 / Base16 / URL encoding and decoding. |
json |
Parse, generate and manipulate JSON. |
xml |
Parse, create, modify and traverse XML documents. |
csv |
Read, write and manipulate CSV files. |
config |
Read/modify/save INI-style configuration files. |
regex |
Regular-expression compile/match/search (PCRE on Windows, POSIX on Linux). |
cli |
Command-line argument/option/sub-command parser. |
log |
Leveled logging (DEBUG…FATAL) to console and/or file. |
file_io |
FileReader / FileWriter with text and binary (UTF-8/UTF-16) modes. |
dir |
Directory and filesystem manipulation. |
| Module | Description |
|---|---|
time |
Time measurement and manipulation (time/std_time.h). |
date |
Gregorian and Persian calendar dates, conversions and arithmetic. |
| Module | Description |
|---|---|
sysinfo |
OS / hardware information (Windows & Linux). |
concurrent |
Threads, mutexes, condition variables, semaphore and a thread pool. |
serial_port |
Serial-port (RS-232) communication. |
| Module | Description |
|---|---|
crypto |
Hashing, encryption/decryption and other primitives (backed by OpenSSL). |
jwt |
JSON Web Token creation/verification (HS/RS/ES/PS families). |
| Module | Description |
|---|---|
network |
TCP and UDP sockets plus a small HTTP server/client. |
database |
PostgreSQL client built on libpq (optional — only built when libpq is present). |
| Module | Description |
|---|---|
plot |
2-D plotting (line/scatter/bar) built on raylib. |
turtle |
Turtle graphics, inspired by Python's turtle (built on raylib). |
| Module | Description |
|---|---|
unittest |
Lightweight unit-testing framework (assertions, suites, fixtures). |
To build the whole project you need:
Build tools - CMake ≥ 3.15 - A C17 compiler — GCC, Clang, or MSVC - A generator — Ninja (reco
$ claude mcp add c_std \
-- python -m otcore.mcp_server <graph>