https://github.com/captkirk88/zevy-mem
A bucket of different Zig allocators for different uses.
https://github.com/captkirk88/zevy-mem
allocators memory-allocation memory-leak-detection zevy zevy-ecs zig
Last synced: 3 months ago
JSON representation
A bucket of different Zig allocators for different uses.
- Host: GitHub
- URL: https://github.com/captkirk88/zevy-mem
- Owner: captkirk88
- License: mit
- Created: 2025-12-07T02:53:47.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2025-12-25T04:01:29.000Z (3 months ago)
- Last Synced: 2025-12-25T15:59:49.615Z (3 months ago)
- Topics: allocators, memory-allocation, memory-leak-detection, zevy, zevy-ecs, zig
- Language: Zig
- Homepage:
- Size: 101 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# zevy-mem
A collection of memory allocators and utilities for Zig.
[license]: https://img.shields.io/github/license/captkirk88/zevy-mem?style=for-the-badge&logo=opensourcehardware&label=License&logoColor=C0CAF5&labelColor=414868&color=8c73cc
[![][license]](https://github.com/captkirk88/zevy-mem/blob/main/LICENSE)
[](https://ziglang.org/)
## Features
- **Safe Allocator**: Allocation tracking with leak detection, double-free prevention, and allocation statistics
- **Guarded Allocator**: Wrap allocators that don't segfault to detect buffer overflows using guard pages
- **Stack Allocator**: Fast bump allocator with LIFO freeing, supports heap-free operation with external buffers
- **Debug Allocator**: Full allocation tracking with leak detection, statistics, and source location resolution
- **Pool Allocator**: O(1) fixed-size object allocation with zero fragmentation
- **Scoped Allocator**: RAII-style allocation scopes with automatic cleanup
- **Counting Allocator**: Simple wrapper for tracking allocation counts and bytes
- **Memory Utilities**: Alignment helpers, byte formatting, and memory region tools
- **Zero External Dependencies**: Pure Zig implementation with no external dependencies
- **Safe Pointers**: Reference-counted pointers (Rc and Arc) for safe memory management
- **Mutex**: Simple mutex implementation for thread safety
## Installation
Add to your `build.zig.zon`:
```bash
zig fetch --save git+https://github.com/captkirk88/zevy-mem
```
Then in your `build.zig`:
```zig
const zevy_mem = b.dependency("zevy_mem", .{});
exe.root_module.addImport("zevy_mem", zevy_mem.module("zevy_mem"));
```
## Quick Start
### Stack Allocator
```zig
const mem = @import("zevy_mem");
// Create with external buffer (no heap)
var buffer: [4096]u8 = undefined;
var stack = mem.StackAllocator.initBuffer(&buffer);
const allocator = stack.allocator();
// Allocate memory
const data = try allocator.alloc(u8, 100);
defer allocator.free(data);
// Check usage
std.debug.print("Used: {} bytes, Remaining: {} bytes\n", .{
stack.bytesUsed(),
stack.bytesRemaining(),
});
```
### Debug Allocator (Leak Detection)
```zig
const mem = @import("zevy_mem");
var debug = mem.DebugAllocator(64).init(std.heap.page_allocator);
const allocator = debug.allocator();
const a = try allocator.alloc(u8, 100);
const b = try allocator.alloc(u8, 200);
// Intentionally leak 'a'
allocator.free(b);
// Check for leaks
if (debug.detectLeaks()) {
debug.dumpLeaks(); // Prints detailed leak report
}
// Get statistics
const stats = debug.getStats();
std.debug.print("Peak allocations: {}\n", .{stats.peak_active_allocations});
```
### Safe Allocator (Leak Detection & Double-Free Prevention)
```zig
const mem = @import("zevy_mem");
var safe = mem.SafeAllocator.init(std.heap.page_allocator, std.testing.allocator);
defer safe.deinit();
const allocator = safe.allocator();
const data = try allocator.alloc(u8, 100);
allocator.free(data);
// Attempt double-free (will panic)
allocator.free(data); // Panic: Double free detected
// defer safe.deinit() will check for leaks and panic if any remain
```
### Pool Allocator
```zig
const mem = @import("zevy_mem");
const Entity = struct {
id: u32,
x: f32,
y: f32,
active: bool,
};
// Create pool with external buffer
var buffer: [100]mem.PoolAllocator(Entity).Slot = undefined;
var pool = mem.PoolAllocator(Entity).initBuffer(&buffer); // .initHeap(...) for heap allocation
// O(1) allocation
const entity = pool.create(.{
.id = 1,
.x = 0.0,
.y = 0.0,
.active = true,
}).?;
// O(1) deallocation
pool.free(entity);
std.debug.print("Pool: {}/{} slots used\n", .{pool.count(), pool.capacity()});
```
### Scoped Allocator
```zig
const mem = @import("zevy_mem");
var buffer: [4096]u8 = undefined;
var stack = mem.StackAllocator.initBuffer(&buffer);
// Permanent allocation
const permanent = try stack.allocator().alloc(u8, 100);
// Scoped temporary allocations
{
var scope = mem.ScopedAllocator.begin(&stack); // Save current state
defer scope.end() catch {}; // Restore state, freeing all scope allocations
const temp1 = try scope.allocator().alloc(u8, 500);
const temp2 = try scope.allocator().alloc(u8, 300);
// Use temp1 and temp2...
_ = temp1;
_ = temp2;
}
// Only 'permanent' remains allocated
std.debug.print("Bytes used: {}\n", .{stack.bytesUsed()}); // 100
_ = permanent;
```
### Nested Scopes
```zig
const mem = @import("zevy_mem");
var buffer: [4096]u8 = undefined;
var stack = mem.StackAllocator.initBuffer(&buffer);
var nested = mem.NestedScope(8).init(&stack);
// Level 0
_ = try nested.allocator().alloc(u8, 100);
// Push level 1
try nested.push();
_ = try nested.allocator().alloc(u8, 200);
// Push level 2
try nested.push();
_ = try nested.allocator().alloc(u8, 300);
// Pop back to level 1 (frees 300 bytes from level 2)
try nested.pop();
std.debug.print("Depth: {}, Bytes: {}\n", .{nested.currentDepth(), stack.bytesUsed()});
// Pop back to level 0 (frees 200 bytes from level 1)
try nested.pop();
```
### Guarded Allocator (Buffer Overflow Detection)
```zig
const mem = @import("zevy_mem");
// Add guard pages around allocations to detect overflows
var guarded = try mem.GuardedAllocator.init(std.heap.smp_allocator, std.testing.allocator, 1);
defer guarded.deinit();
const allocator = guarded.allocator();
// Allocate with guard pages
const buf = try allocator.alloc(u8, 100);
defer allocator.free(buf);
// Overflowing beyond allocated size will cause segmentation fault
// buf[100] = 42; // Would segfault due to guard page
```
## Examples
### Game Frame Allocator Pattern
```zig
const mem = @import("zevy_mem");
const FrameAllocator = struct {
stack: mem.StackAllocator,
buffer: [1024 * 1024]u8, // 1MB per frame
pub fn init() FrameAllocator {
var self = FrameAllocator{
.stack = undefined,
.buffer = undefined,
};
self.stack = mem.StackAllocator.initBuffer(&self.buffer);
return self;
}
pub fn allocator(self: *FrameAllocator) std.mem.Allocator {
return self.stack.allocator();
}
pub fn reset(self: *FrameAllocator) void {
self.stack.reset();
}
};
// Usage in game loop
var frame_alloc = FrameAllocator.init();
while (running) {
// All frame allocations automatically cleaned up
defer frame_alloc.reset();
const temp_data = try frame_alloc.allocator().alloc(u8, 1000);
// Use temp_data for this frame...
_ = temp_data;
}
```
### Component Pool for ECS
```zig
const mem = @import("zevy_mem");
const Position = struct { x: f32, y: f32, z: f32 };
const Velocity = struct { x: f32, y: f32, z: f32 };
const ComponentPools = struct {
positions: mem.PoolAllocator(Position),
velocities: mem.PoolAllocator(Velocity),
pos_buffer: [1000]mem.PoolAllocator(Position).Slot,
vel_buffer: [1000]mem.PoolAllocator(Velocity).Slot,
pub fn init() ComponentPools {
var self: ComponentPools = undefined;
self.positions = mem.PoolAllocator(Position).initBuffer(&self.pos_buffer);
self.velocities = mem.PoolAllocator(Velocity).initBuffer(&self.vel_buffer);
return self;
}
};
var pools = ComponentPools.init();
const pos = pools.positions.create(.{ .x = 0, .y = 0, .z = 0 }).?;
const vel = pools.velocities.create(.{ .x = 1, .y = 0, .z = 0 }).?;
_ = pos;
_ = vel;
```
### Debug Memory Tracking
```zig
const mem = @import("zevy_mem");
pub fn runWithMemoryTracking() !void {
var buffer: [64 * 1024]u8 = undefined;
var debug = mem.DebugStackAllocator(256).initBuffer(&buffer);
defer {
if (debug.detectLeaks()) {
debug.dumpLeaks();
@panic("Memory leaks detected!");
}
}
const allocator = debug.allocator();
// Your code here...
const data = try allocator.alloc(u8, 100);
defer allocator.free(data);
// Print final stats
const stats = debug.getStats();
std.debug.print("Peak memory: {} bytes\n", .{stats.peak_bytes_used});
std.debug.print("Total allocations: {}\n", .{stats.total_allocations});
}
```
## Performance
| Allocator | Alloc | Free | Memory Overhead |
|-----------|-------|------|-----------------|
| SafeAllocator | O(1) | O(1) | ~32 bytes/allocation |
| GuardedAllocator | O(1) | O(1) | 2 * guard_pages * page_size per allocation |
| StackAllocator | O(1) | O(1)* | 0 bytes |
| DebugAllocator | O(1) | O(n)** | ~72 bytes/allocation |
| PoolAllocator | O(1) | O(1) | max(sizeof(T), 8) per slot*** |
| ScopedAllocator | O(1) | O(1) | 24 bytes per scope |
| NestedScope | O(1) | O(1) | 16 bytes per depth level |
| CountingAllocator | O(1) | O(1) | 24 bytes (wrapper state) |
\* Only LIFO frees reclaim memory
\** n = number of tracked allocations (linear search in `findAllocation`)
\*** Slot size is `@max(@sizeOf(T), @sizeOf(?*anyopaque))` to accommodate the free list pointer
## Limitations
- **LIFO Freeing**: Stack allocators only reclaim memory for the most recent allocation
- **Fixed Capacity**: Pool and debug allocators have compile-time limits
- **No Thread Safety**: All allocators are single-threaded
- **Buffer Ownership**: Caller manages buffer lifetime
- **SafeAllocator**: Runtime checks with panics; not suitable for production error handling
- **GuardedAllocator**: High memory overhead from guard pages; segfaults may not provide detailed error info
## Contributing
Contributions, issues, and feature requests are welcome! Please open an issue or submit a pull request.
## Related Projects
- [zevy-ecs](https://github.com/captkirk88/zevy-ecs) - Entity Component System framework
- [zevy-reflect](https://github.com/captkirk88/zevy-reflect) - Reflection and change detection