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
