nonstd is a single-header C library providing a collection of "non-standard" but highly useful utilities, data structures, and type definitions that are often missing from the C standard library. It aims to make C programming more ergonomic and productive. It aims to be C99 compliant.
Important
There are more involved libraries out there providing better granularity and functionality. This library is intended for ease of use first and foremost. It is not a replacement for those libraries. Use at your own risk.
Features
- Shorthand Types: Concise integer types (
i8,u32,usize, etc.) for better readability. - Utility Macros: Common helpers like
countof,MIN,MAX,CLAMP, andstatic_foreach. - String View (
stringv): Non-owning, read-only string references to avoid unnecessary copies. - String Builder (
stringb): Growable, mutable string buffer for efficient string construction. - Dynamic Array (
array): Generic growable arrays implemented via macros (similar tostd::vectorin C++). - Slices (
slice): Generic non-owning views into arrays. - Memory Arena: Simple block-based arena allocator for bulk memory management.
- File I/O: Helper functions to read and write entire files with a single call.
- Logging: Simple, leveled logging with ANSI colors and timestamps.
- Canvas & PPM: Simple 2D drawing API with PPM (ASCII) import/export.
Installation
nonstd is a single-header library. To use it:
- Copy
nonstd.hinto your project's include directory. - In one C source file, define
NONSTD_IMPLEMENTATIONbefore including the header to create the implementation:
#define NONSTD_IMPLEMENTATION
#include "nonstd.h"
- In other files, just include it normally:
#include "nonstd.h"
Usage
Check the examples directory for usage examples. But here are a few simple
examples:
1. Basic Types & Macros
#include "nonstd.h"
void example() {
i32 x = 10;
u64 y = 2000;
usize size = 1024;
int numbers[] = {1, 2, 3, 4, 5};
printf("Count: %zu\n", countof(numbers)); // 5
int val = CLAMP(100, 0, 10); // 10
// Static foreach loop
int n;
static_foreach(int, n, numbers) {
printf("%d ", n);
}
}
2. Dynamic Arrays
Create type-safe growable arrays for any type.
// Define an array of integers
array(int) numbers;
array_init(numbers);
// Push values
array_push(numbers, 10);
array_push(numbers, 20);
array_push(numbers, 30);
// Iterate (index basic loop or helper macro)
int val;
array_foreach(numbers, val) {
printf("%d\n", val);
}
// Access by index
if (numbers.length > 0) {
printf("First: %d\n", numbers.data[0]);
// or
printf("First: %d\n", array_get(numbers, 0));
}
// Clean up
array_free(numbers);
3. String Views & Builders
String View (stringv):
Ideal for parsing and passing strings around without allocation.
const char *raw = "Hello World";
stringv sv = sv_from_cstr(raw);
stringv word = sv_slice(sv, 0, 5); // "Hello" (no allocation)
if (sv_starts_with(sv, sv_from_cstr("Hello"))) {
// ...
}
String Builder (stringb):
Efficiently construct strings.
stringb sb;
sb_init(&sb, 0); // 0 = default capacity
sb_append_cstr(&sb, "Hello");
sb_append_char(&sb, ' ');
sb_append_cstr(&sb, "World");
printf("%s\n", sb.data); // "Hello World"
sb_free(&sb);
4. Memory Arena
Efficiently allocate many small objects and free them all at once.
Arena arena = arena_make();
// Allocations are fast and contiguous within blocks
void *obj1 = arena_alloc(&arena, 64);
void *obj2 = arena_alloc(&arena, 128);
// growth is automatic if a block is full
// Free everything at once
arena_free(&arena);
5. File I/O Helpers
Read or write files with a single functional call.
size_t size;
char *content = read_entire_file("data.txt", &size);
if (content) {
printf("Read %zu bytes:\n%s\n", size, content);
free(content); // Standard free (unless using arena)
}
// Or read directly into a string builder
stringb file_sb = read_entire_file_sb("config.ini");
// ... use file_sb ...
sb_free(&file_sb);
6. Logging
Simple logging with levels (ERROR, WARN, INFO, DEBUG), timestamps, and colors.
// Set log level (default is INFO)
set_log_level(LOG_DEBUG);
// Use macros for logging
LOG_INFO_MSG("Starting application...");
LOG_DEBUG_MSG("Variable x = %d", 42);
LOG_WARN_MSG("Low memory warning");
LOG_ERROR_MSG("Connection failed");
// Environment variable override supported:
// LOG_LEVEL=0 (ERROR) ... 3 (DEBUG)
7. Canvas & PPM Images
Create simple 2D images, draw shapes, and save to PPM (ASCII) format.
// Initialize a 400x400 canvas
Canvas canvas = ppm_init(400, 400);
// Use predefined colors or HEX/RGB macros
ppm_fill(&canvas, COLOR_HEX(0x1a1a1a));
// Draw basic shapes
ppm_draw_rect(&canvas, 50, 50, 100, 100, COLOR_RED);
ppm_draw_circle(&canvas, 250, 100, 40, COLOR_BLUE);
ppm_draw_line(&canvas, 200, 200, 350, 350, COLOR_GREEN);
ppm_draw_triangle(&canvas, 50, 350, 150, 350, 100, 250, COLOR_YELLOW);
// Save to file
if (ppm_save(&canvas, "output.ppm")) {
printf("Image saved successfully!\n");
}
// Clean up
ppm_free(&canvas);
Testing
The project includes a test suite using minunit.
To build and run the tests:
make test