Library / SDK
stclib/STC avatar
stclib/STC

STC for C99: generic containers, macro templates and a 6.0 RC4 tree

A modern, user friendly, generic, type-safe and fast C99 container library: String, Vector, Sorted and Unordered Map and Set, Deque, Forward List, Smart Pointers, Bitset and Random numbers.

1,994 stars120 forksCMIT

At a glance

What is it?
STC fills gaps in C99 with generic containers, strings, spans, coroutines and regular expressions, all instantiated by macros inside the headers you include. Its strength is type-checked data structures without compiler extensions; its cost is an API its own notes call unstable between 5.1 and 6.0.
Who is it for?
Adopt STC if you write C99 without compiler extensions and want containers that fail at compile time rather than at run time; the price is one include per data structure plus a macro vocabulary to learn. Pass if you need a stable public C API, because the project's own notes promise stability only from 6.0 onward.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 15, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One header per container, instantiated by the preprocessor

Every data structure is a header you instantiate yourself: name the type, the key and the value, then include that one container header. Since 5.0 a single T template parameter can declare the container type together with `i_key` and `i_val`, or `i_opt` for a struct with an optional member, on one line. 5.1 went further and let users define T as a shorthand for `i_type`, and added a single `#define` ahead of the include for element types carrying `_drop()` and `_clone()`, selected through the `c_class_key`, `c_pro_key` and `c_comp_key` traits.

No type checking happens at run time. Where generic code must cast, `c_static_assert()`, `c_as_mut()` and `c_safe_cast()` do the work at compile time, and the stated design goal is no opaque pointers and no casting away of type information. Indices and sizes are signed throughout containers and algorithms, with unsigned integers reserved for bitsets and bit operations, on the grounds that mixed signed and unsigned comparisons quietly give the wrong answer.

`#define i_aux <TYPE>` is the seam for extending a templated container, and both i_aux and custom allocation bugs sit on the 6.0 fix list, which is where that extension point has been weakest.

A sum type that hides the inactive payload

A plain union paired with an enum tag in C has no rule forcing the payload to match that tag, and nothing stops user code from reading the inactive member. STC's sum type, shipped through `algorithm.h`, is the typesafe version of the pattern: the inactive payload stays out of reach, so the tag and the value cannot drift apart.

Strings come in three shapes. The owning `cstr` has utf8 support and short string optimization, `csview` is a non-zero terminated view, and `zsview` is zero-terminated. Choosing between the two views is an API decision with teeth, since handing a `csview` to something that expects a C string is exactly the class of bug this library exists to remove.

`cspan` goes further: a dynamic multi-dimensional subspan view with numpy-like slicing and arbitrary array dimensions, so a two dimensional buffer can be read with slice notation instead of hand written row and column arithmetic. Views hold no ownership, which means the buffer underneath still has to outlive every view taken from it.

Eleven runtime modules become one static archive

Not all of STC is header-only. `LIB_LIST` names the compiled parts, and `LIB_SRCS` expands each name to a C file under src/:

code
LIB_NAME  := stc
LIB_LIST  := cstr_core cstr_misc cstr_io cstr_utf8 utf8_misc cregex csview cspan fmt random stc_core
LIB_PATH  := $(BUILDDIR)/lib$(LIB_NAME).a

That output path is not fixed. `BUILDDIR` becomes `build/$(shell uname)_$(CC)` on Linux and Mac, and `build/Windows_$(CC)` when `$(OS)` is Windows_NT, so gcc, clang and tcc results never land on top of each other. Archiving also switches on the compiler: `ar rcs` by default, and `tcc -ar rcs` when `$(CC)` is tcc.

Examples are collected by wildcard rather than listed by hand, with `EX_SRCS` covering `examples/*/*.c` and each source becoming its own executable inside that build directory. Tests are gathered the same way into `TEST_SRCS` from `tests/*_test.c` plus `tests/main.c`, but the Makefile text ends before the rule that links them, so how the test binary gets built is not something this repository shows.

Building the examples with gcc, clang or tcc

The compiler comes from the environment, and gcc is the fallback whenever `CC` has not been set:

code
ifeq ($(origin CC),default)
	CC := gcc
endif

So `make CC=gcc` selects gcc explicitly, and clang or tcc can be picked the same way. The warning set is where you will feel the library most:

code
CFLAGS    ?= -std=c11 -Iinclude -O3 -MMD -Werror -Wpedantic -Wall -Wextra -Wconversion -Wno-missing-field-initializers
CXXFLAGS  ?= -std=c++20 -Iinclude -O3 -MMD -Wall

Two of those flags deserve a pause. `-Werror` turns any new warning in your own code into a build failure, and `-Wconversion` is strict enough that the Makefile adds `-Wno-clobbered` for gcc but not clang, after probing the preprocessor for `__clang__`.

code
ifeq ($(CC),tcc)
  AR_RCS  ?= tcc -ar rcs
else
  AR_RCS  ?= ar rcs
endif

On Windows the makefile needs mkdir, rm and printf from `C:\Program Files\Git\usr\bin`, and the platform branch also decides what gets linked:

code
ifeq ($(OS),Windows_NT)
	DOTEXE := .exe
	BUILDDIR := build/Windows_$(CC)
	LDFLAGS += -fopenmp
else
#	CC_VER := $(shell $(CC) -dumpversion | cut -f1 -d.)
	BUILDDIR := build/$(shell uname)_$(CC)
	LDFLAGS += -lm

Expect every example in the examples/ tree to end up as a separate binary in the build directory, and expect no debug configuration, since the Makefile states that only an optimised build is provided.

Robin Hood hashing beside a binary tree map

Two maps ship side by side and they are not interchangeable. `hashmap` is the unordered one, and as of 5.0 it uses Robin Hood hashing, described as very fast on the clang compiler. `sortedmap` is a binary tree map, so iteration comes out in key order and each lookup pays for tree descent rather than a hash probe. Take the tree when ordering is part of your interface, and the hash map when throughput is.

The unordered side has also been the least settled part of the library. The 6.0 fix list names missing exports in hmap, a `declare_hash_set()` that had to be provided, and a general hash function bug, while switching to Robin Hood hashing was itself a 5.0 change. If you move a codebase from 5.x to 6.0, exercise both containers with your own tests rather than assuming that unchanged container names mean unchanged behaviour.

The queue family splits the same way three times: `queue`, `deque` for both ends, and `pqueue` when elements must come out by priority. The vector type and the `cspan` view make the identical owning versus borrowing choice.

The one-pointer arc and what it will not wrap

The two smart pointers split ownership differently: box holds a heap allocated unique pointer, while arc is an atomic reference counted shared pointer. In 5.1 arc was rewritten to occupy a single pointer, and the earlier implementation was kept reachable through the `c_use_arc2` trait. The catch is stated plainly: the new arc cannot be constructed from an object pointer, and arc2 is the type for that case. A project that adopted 5.0 arc and wraps borrowed objects faces a different upgrade path.

Both types enter containers through the `i_pro_key` and `i_pro_val` template parameters, which is how one container declaration can hold `cstr`, box and arc values generically. Hand written types with `_drop()` and `_clone()` are covered by `i_class_key` and `i_class_val`. Custom allocations and i_aux are the two entries on the 6.0 fix list that touch this area, so a custom allocator meeting the library is exactly where to test hardest.

Coroutines and cregex carry the promised API break

Read the 6.0 news before pinning a version. The project apologises for repeated API changes on the coroutine, cspan and cregex modules after 5.1, then promises that the API is planned to be stable from 6.0 with backward compatible changes. Coroutines took the largest rewrite: V2 coroutines with waitgroups, cancellation and async finalization, on top of the structured concurrency and symmetric coroutines introduced in 5.0.

Smaller changes are likelier to break a build than the headline ones. `c_foreach (...)` became `for (c_each(...))` and `c_forrange(...)` became `for (c_range(...))` in 5.0.2. cregex gained a new API and a word-boundary match fix under #149, and it was taught to handle invalid utf8 strings in 5.1.

On the correctness side, #143 is described as a major container initialization flaw and #146 as a cstr initialization regression, with #142 a regression around i_no_clone. Containers written against 5.x deserve a second pass rather than a version bump.

A 6.0 RC4 header over a tree whose tags stop at v5.0

The README header says Version 6.0 RC4, while the newest tag on the repository is v5.0, dated 2025-01-12. Those two facts do not line up, and neither does the distance between them, because the 6.0 notes describe that version as upcoming and the tree has moved a long way since. Anyone who wants the 6.0 RC4 behaviour has to build from main rather than pick a tag.

Two smaller mismatches are worth naming. The library describes itself as C99/C11, yet the Makefile defines `CXXFLAGS` with `-std=c++20` and defaults `CXX` to g+, so the headers are expected to survive a C++20 toolchain as well. And the docs index covers seventeen container and view pages while the compiled archive carries two modules, fmt and random, with no docs page and no examples directory; random numbers appear in the project description but not in that index.

STC is MIT licensed and the last push landed on 2026-09-29, so the branch is current even though the newest packaged build is old.

Editorial conclusion

Adopt STC if you write C99 without compiler extensions and want containers that fail at compile time rather than at run time; the price is one include per data structure plus a macro vocabulary to learn. Pass if you need a stable public C API, because the project's own notes promise stability only from 6.0 onward. Before pinning a dependency, diff your warning flags against the -Werror set, count which src/ modules your build compiles, and re-run your own tests against hashmap and cstr after a 6.0 upgrade.

Frequently asked questions

How do I add STC to an existing C project?

Each container is a header you instantiate with your own types, while the non-templated runtime parts live as C files under src/, so a build either compiles those modules or links the archive the Makefile produces. The README gives no package manager install, so the repository itself is the distribution channel.

Does STC work in C++ as well as in C?

The Makefile sets CXXFLAGS to -std=c++20 with -Iinclude and defaults CXX to g+, so the project's own build compiles the headers in a C++20 context. The README still describes the library as targeting C99/C11.

Which version of STC should a new dependency pin?

The newest release is v5.0 from 2025-01-12, while the tree on main carries a Version 6.0 RC4 README header. That gap is real, because the same news block calls 6.0 upcoming, so fixes listed for #143, #146 and #138 exist only on the default branch.

Is STC still maintained?

The last push on the default branch main was on 2026-09-29 and the repository is not archived. Recent work has been concentrated on 6.0 fixes rather than on new packaged builds, so activity and stability are not the same thing here.

How do I choose between sortedmap and hashmap?

Use sortedmap when ordered iteration matters, since it is a binary tree map, and hashmap when lookup speed matters, since it uses Robin Hood hashing as of 5.0. Both had export, declaration and hash function fixes queued for 6.0, so treat a major upgrade as a retest for either one.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. stclib/STC on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stclib-stc.svg)](https://hysenlabs.com/projects/stclib-stc)