1
0
Fork
You've already forked ctb
0
C ToolBox
  • C 93.4%
  • Makefile 6.6%
2026年03月02日 10:52:58 +01:00
tests build in /_o 2026年03月02日 10:52:58 +01:00
.gitignore build in /_o 2026年03月02日 10:52:58 +01:00
arg.h fixed EARGF() to not abort (follow doc) 2024年12月17日 12:46:45 +01:00
ator.h memory size type 2026年03月02日 10:49:32 +01:00
LICENSE change pmem size type and add license 2026年02月19日 12:47:12 +01:00
pmem.h memory size type 2026年03月02日 10:49:32 +01:00
README.md doc pmem with simple example 2026年02月19日 12:56:27 +01:00

My little set of C utilities

License

As most of this stuff is taken/adapted from free or public domain source code from other people, it is published in a dual permissive license: public domain unlicense.org or MIT. See LICENSE file for details.

arg.h

Quick parse command line arguments. Taken from suckless tools, with a few changes:

  • argc and argv are not hardcoded, but are defaults. Define the macros ARGC and ARGV to rename them
  • argv0 is not hardcoded and can be declared after the include, with the default ARGVAR variable name (which should be a const char **). Define the macro ARGVAR to rename it
  • ARG represents the current option argument letter (type char)
  • EARGF(x) does not abort() but let the called function handling terminaison

Complete example with added features:

#define ARGC arg_count
#define ARGV arg_ptrs
#define ARGVAR program_name
#include "arg.h"
static const char *program_name;
static void
usage(void) {
 /* print error and exit */
}
int
main(int arg_count, const char **arg_ptrs) {
 {
 unsigned char boolean_opt = 0;
 const char *display_opt = 0;
 const char *file_required = 0;
 /* parse command line arguments */
 ARGBEGIN {
 case 'b': boolean_opt = 1; break;
 case 'd': display_opt = ARGF(); break;
 case 'f': file_required = EARGF(usage()); break;
 } ARGEND;
 /* process options */
 }
 return 0;
}

ator.h

Define an allocator interface that can be customized. It got its origin in the Nuklear project where memory handling is not bound to the standard library.

The struct ator is very simple:

  • a mem() function pointer doing allocation and deallocation (see below)
  • a userdata pointer for any custom data needed by a custom allocator

3 macros then do a shortcut over the mem() function with file/line references:

  • ATOR_ALLOC(ator, size) allocates size bytes with ator allocator
  • ATOR_REALLOC(ator, ptr, size) reallocates size bytes with ator allocator on ptr, returning the modified pointer
  • ATOR_FREE(ator, ptr) frees memory allocated on ptr by ator allocator

The mem() function has the prototype:

void *mem(void *userdata, void *ptr, ator_size size, const char *file, int line);

ator_size is the size_t equivalent without needing to include a standard library header (see below for changing the implementation).

The behavior is as follow:

  • if ptr is null and size is 0, nothing is done (0 is returned)
  • if ptr is null and size not 0, allocates size bytes and returns the pointer
  • if ptr is not null and size is 0, frees memory on ptr, returns null
  • if ptr is not null and size not 0, reallocates size bytes from previously allocated memory on ptr and returns the new pointer

Usage

The following macros can be defined prior inclusion:

Macro Description
ATOR_IMPLEMENTATION define in one (and only one) source file, useful only for ATOR_STD_ALLOC
ATOR_STD_ASSERT use assert() from assert.h
ATOR_STD_TYPES use size_t from stddef.h for ator_size type
ATOR_STD_ALLOC define a ator_std_init() function to create an allocator using realloc() and free()

Some behavior can be overriden by defining some other macros:

Macro Description
ATOR_ASSERT use your custom assertion compatible with the standard one
ATOR_SIZE_TYPE use a specific size_t equivalent

Simple example using std allocator:

#define ATOR_IMPLEMENTATION
#define ATOR_STD_SIZE
#define ATOR_STD_ASSERT
#define ATOR_STD_ALLOC
#include "ator.h"#include <string.h>
int
main(int argc, char **argv) {
 struct ator ator = {0};
 char *ptr = 0;
 // use ator the same than malloc(), realloc(), free()
 ator_std_init(&ator, 0);
 ptr = ATOR_ALLOC(ator, 32);
 strcpy(ptr, "This is a test");
 ATOR_FREE(ator, ptr);
 return 0;
}

pmem

Basic portable MMU based memory usage.

Handles mapping between virtual and really allocated memory.

Hides OS implementation to reserve a range of continuous address space, that is ensured to be available for allocation without using RAM.

From this reserved space the application can allocate blocks of data, actually using physical memory. This permits to have a simple memory management through arenas, pools or stacks without preallocating the full memory size.

virtual memory (address space)
+-----------------------------------------------------------------------------+
| ............................................. |
+-----------------------------------------------------------------------------+
 ^ reserved with pmem_reserve() ^
allocated
+-----------------------------------------------------------------------------+
| ............................................. |
| | block1 | block2 | block3 |
+-----------------------------------------------------------------------------+
 ^ ^^ ^^ ^
 pmem_alloc() pmem_alloc() pmem_alloc()

Application is still in charge to manage allocated memory from the reserved address space, and freeing it if needed (note that freeing requires to specify the size of freed memory).

By default memory size is coded in a simple long (which is signed 64bit on most platforms). This can be customized by defining PMEM_SIZE_TYPE before including pmem.h. That enables usage of half the virtual address space which is more than enough (I even started the project with a simple int limiting memory size to 2GB).

Step 1: reserve address space

int main(void) {
 pmem m = {0};
 void *ptr, *next;
 m = pmem_reserve(16 * 1024 * 1024); /* reserve 16MB */
 /* for most usage (reserving address space for the lifetime of the program)
 * pmem_release() is optional if not checking leaks during debug
 */
#ifdef _DEBUG
 pmem_release(m);
#endif
 return 0;
}

Step 2: allocate block of memory from reserved space

void
use_mem(pmem m) {
 void *ptr, *next;
 PMEM_SIZE_TYPE size1 = 16 * 1024;
 PMEM_SIZE_TYPE size2 = 8 * 1024;
 ptr = m.ptr;
 next = ptr + size1;
 pmem_alloc(ptr, size1); /* allocate 16KB at the start of reserved mem */
 memset(ptr, 0, size1);
 pmem_alloc(next, size2);
 /* do things with memory at ptr (16K available) and next (8K available) */
 /* deallocation order is not crucial */
 pmem_free(ptr, size1);
 pmem_free(next, size2);
}