Sp.h is the standard library that C deserves

dboon2 pts0 comments

sp.h is the standard library that C deservessp.h is the standard library that C deserves<br>in which we discuss primitives<br>2026/05/20

Over the past year, I&rsquo;ve been working on fixing C by giving it a high quality, ultra portable standard library. It is not a simple wrapper on top of libc; it doesn&rsquo;t depend on libc except when required to by the platform. To my knowledge, there is nothing like it.<br>The library is called sp.h1. It&rsquo;s a 15,000 line, single header library written in plain C99. You can find the source code on GitHub, which includes the library itself, lots of example programs, and half a dozen baseball libraries2 which extend the core. If you prefer to read a few examples and look through the source, head to GitHub first. Otherwise, let&rsquo;s get on with the pitch!<br>Table of Contents<br>Principles<br>Program directly against syscalls<br>The fundamental idea is that any C standard library must be written directly against the lowest level primitives available3. It is neither useful nor productive to try to emulate, produce, or interface with the decades of cruft that have accumulated between the OS and the code that you yourself write.<br>Libc is actively harmful<br>It is tempting to conform to libc, because swaths of code promise to compile and run if you can simply provide an implementation of libc. But more and more, this is untrue.<br>Libc does not provide a useful interface for any program. Simple programs would rather use a high level language. Sophisticated programs cannot be written with the primitives that it provides. This has been exacerbated over the past decade as asynchronous programming has become more important. A &ldquo;fast&rdquo; program is becoming less about solving e.g. register allocation better than the other compiler and more about e.g. using the right kernel primitives to do IO.<br>Any interface upon which the fundamental unit of IO is FILE* or upon which a substring is a malformed idea is not just annoying. It&rsquo;s harmful. sp.h casts it aside4.<br>There Is No Heap<br>These types underpin the entire library:<br>typedef enum {<br>SP_ALLOCATOR_MODE_ALLOC,<br>SP_ALLOCATOR_MODE_FREE,<br>SP_ALLOCATOR_MODE_RESIZE,<br>} sp_mem_alloc_mode_t;

SP_TYPEDEF_FN(<br>void*,<br>sp_allocator_fn_t,<br>void* user_data, sp_mem_alloc_mode_t mode, u64 size, void* ptr<br>);

typedef struct sp_allocator_t {<br>sp_allocator_fn_t on_alloc;<br>void* user_data;<br>} sp_mem_t;

They do so by forcing programs to accept that &ldquo;the ability to allocate any amount of memory from the ether&rdquo; is not a primitive; it is a fiction. Memory is not owned by &ldquo;the runtime&rdquo;. Memory is owned by your program.<br>Null-terminated strings are the devil&rsquo;s work<br>I have written about this in the past<br>Null terminated strings mean you cannot:<br>Return a non-owning substring<br>Know the length of a string in O(1)<br>Write lexers and parsers which return ergonomic views into source<br>Build strings without invalid intermediate values<br>Plus, of course, the unfathomable number of bugs and security issues that arise from a missing null terminator. Step one to modernizing C is to completely ditch null terminated strings in favor of the humble sp_str_t.

The only downside, I believed, was that you were forced to make an extra copy to interface with any other C API you might come across. I have come to find that this is completely meaningless.<br>A C standard library built natively around pointer + length strings is shockingly ergonomic. For example, a snippet from a wc clone:<br>sp_str_t content = sp_zero;<br>sp_io_read_file(mem, path, &content);

sp_ht(sp_str_t, u32) counts = sp_zero;<br>sp_str_ht_init(mem, counts);<br>sp_da(sp_str_t) lines = sp_str_split_c8(mem, content, '\n');<br>sp_da_for(lines, i) {<br>sp_da(sp_str_t) words = sp_str_split_c8(mem, lines[i], ' ');

sp_da_for(words, j) {<br>u32* count = sp_str_ht_get(counts, words[j]);<br>if (count) {<br>*count = *count + 1;<br>} else {<br>sp_str_ht_insert(counts, words[j], 1);

If your first reaction is &ldquo;so what?&rdquo;, then, yeah, that&rsquo;s the point. Here&rsquo;s a piece of C code which reads roughly like any high level language but also never copies data from the source buffer while parsing. In other words, it&rsquo;s both the most ergonomic version and the most performant version.<br>Be a part of your software, not aside from it<br>The library is meant to be read, modified, tweaked, rewritten, or whatever verb you might need to have it serve your purposes. I&rsquo;ve worked very hard to this end:<br>The core of the library is ~40 syscalls which are the only platform specific code5<br>The library ships as a single file which needs no configuration<br>The file is extremely organized, and tagged with @tags for human or LLM search<br>Every function is part of a namespace<br>Where the frustrating parts of C seek to hide the OS your program runs on behind an elaborate fiction, sp.h seeks to unify only those things which are true, as thinly as possible while being useful, and then building functionality on top of the exact same primitives that it gives...

library rsquo standard libc primitives program

Related Articles