Memory Allocation
In mruby, you can customize how memory is allocated in two ways:
- Provide your own
malloc()/realloc()/free() - Override
mrb_basic_alloc_func()
1. Provide your own malloc()/realloc()/free()
On platforms without a full C standard library —such as many microcontrollers— you may need to supply your own implementations of malloc(), realloc(), and free(). mruby’s allocator calls directly into these functions, so replacing them lets you control every allocation and deallocation performed by your entire program, including any third‑party libraries you link against.
Keep in mind:
- Calling
realloc(NULL, size)must behave likemalloc(size). - Calling
free(NULL)must be a no‑op.
Simply define these three functions in your code (or link against a library that provides them), and mruby — along with all other code in your process — will use your versions automatically.
2. Override mrb_basic_alloc_func()
Inside mruby, all of its own memory allocations go through a single function called mrb_basic_alloc_func() (formerly mrb_default_allocf()). By defining this function in your application before linking, you can intercept and handle only the memory operations initiated by mruby itself without affecting other libraries or parts of your program.
// Example signature: // void* mrb_basic_alloc_func(void* ptr, size_t size);
Implement mrb_basic_alloc_func() in your code, and mruby will invoke it for every internal allocation, reallocation, and free request.
Expected behavior
mrb_basic_alloc_func(NULL, size)should allocatesizebytes, just likemalloc(size).mrb_basic_alloc_func(ptr, size)should resize the existing block atptrtosizebytes, just likerealloc(ptr, size).mrb_basic_alloc_func(ptr, 0)should free the block atptr, just likefree(ptr).
Summary of effects:
-
Custom
malloc/realloc/free: replaces allocation behavior globally (mruby + all other code and third‑party libraries). -
Custom
mrb_basic_alloc_func(): replaces allocation behavior only for mruby’s internal use, leaving other libraries’ allocations untouched.
Migration note
If you are moving from the old API:
-
Removal of
mrb_open_allocf()-
_Old:
mrb_state *mrb = mrb_open_allocf(my_allocf, ud);
-
_New:
// No allocf parameter; set up your hook via mrb_basic_alloc_func definition. mrb_state *mrb = mrb_open_core();
-
-
mrb_open_core()takes no arguments- Simply drop any allocf or user-data arguments, and redefine
mrb_basic_alloc_funcas you need.
- Simply drop any allocf or user-data arguments, and redefine
-
No more
mrb_allocftype-
Definitions using the
mrb_allocftypedef can be removed; implementmrb_basic_alloc_func()with the signature below:void* mrb_basic_alloc_func(void *ptr, size_t size);
-
-
mrb_basic_alloc_funcsignature change-
Old:
void* mrb_default_allocf(mrb_state *mrb, void *ptr, size_t size, void *ud);
-
New:
void* mrb_basic_alloc_func(void *ptr, size_t size);
-
Code examples
-
Old style:
static void* my_allocf(mrb_state *mrb, void *ud, void *ptr, size_t size) { // ...custom logic... } mrb_state *mrb = mrb_open_allocf(my_allocf, some_ud);
-
New style:
// Define your hook before creating the state: void* mrb_basic_alloc_func(void *ptr, size_t size) { // ...custom logic... } mrb_state *mrb = mrb_open_core();
3. Heap Regions: Contiguous Memory for GC
By default, mruby allocates GC heap pages individually via malloc().
On embedded targets with multiple memory banks (e.g., STM32 CCM+SRAM,
ESP32 PSRAM+IRAM), you may want to place heap pages in a specific
memory region. mrb_gc_add_region() lets you provide a contiguous
buffer that mruby carves into heap pages.
API
#include <mruby/gc.h> int mrb_gc_add_region(mrb_state *mrb, void *start, size_t size);
start: pointer to a contiguous memory buffer.size: size of the buffer in bytes.- Returns: number of heap pages carved from the buffer, or 0 if the buffer is too small.
The buffer is aligned internally to pointer size. Each page is
approximately 40 KB on 64-bit systems (24 KB on 32-bit). The caller
retains ownership of the buffer and must keep it valid for the
lifetime of the mrb_state.
Example: Static buffer
#include <mruby.h> #include <mruby/gc.h> /* 256 KB static buffer -- about 6 pages on 64-bit */ static char heap_buf[256 * 1024]; int main(void) { mrb_state *mrb = mrb_open(); int pages = mrb_gc_add_region(mrb, heap_buf, sizeof(heap_buf)); /* pages are immediately available for object allocation */ /* ... use mrb ... */ mrb_close(mrb); /* region pages are cleaned up; buffer is not freed */ return 0; }
Example: MCU with multiple RAM banks
/* STM32 with 64 KB CCM and 128 KB SRAM */ extern char __ccm_start[], __ccm_end[]; /* linker symbols */ extern char __sram_start[], __sram_end[]; mrb_state *mrb = mrb_open(); mrb_gc_add_region(mrb, __ccm_start, __ccm_end - __ccm_start); mrb_gc_add_region(mrb, __sram_start, __sram_end - __sram_start);
How it works
When mrb_gc_add_region() is called, mruby:
- Aligns the buffer start to pointer size.
- Divides the buffer into
mrb_heap_page-sized chunks. - Initializes each page's freelist and links it into the GC heap.
- Records the region in a descriptor for O(1) pointer-to-page mapping.
Region pages participate in the normal GC cycle (mark-and-sweep) like any other heap page. The only differences are:
- Never freed: the GC will not call
free()on region pages, even if all objects on a page are dead. The page stays in the heap with an empty freelist, ready for reuse. - Fallback: when all region pages are full, mruby falls back to
malloc()for new pages as usual. - Cleanup:
mrb_close()frees the internal region descriptor but does not free the buffer itself.
Sizing
The page size is controlled by MRB_HEAP_PAGE_SIZE (default: 1024 slots).
Each page occupies:
| Platform | Slot size | Page size (approx) |
|---|---|---|
| 64-bit | 40 bytes | ~41 KB |
| 32-bit | 24 bytes | ~25 KB |
To estimate pages for a given buffer: pages = buffer_size / sizeof(mrb_heap_page).
Each page provides MRB_HEAP_PAGE_SIZE object slots.