I’m trying to create a simple C API similar to Lua VM’s API, where anyone can embed the language from any C application.
I wanted to use Zig’s allocators within C, such that the memory useage remains deterministic, and can be tested (with std.heap.GeneralPurposeAllocator’s memory leak detectors.
Since we can’t store std.mem.Allocator in an extern struct, I converted it to an ?*const anyopaque pointer and stored it within the struct.
The code is shown below and seems to work. It can detect memory leaks, double frees, etc.
Though the question is,
Is this safe to do?
(or if this is unsafe, Is there any resources I can follow to do this safely? )
In the end I would want to be able to pass the ExperimentalAllocator to C API functions, such that those functions could manage memory similar to Zig.
I know it seems potentially risky, but it needs to be done.
Thanks.
/// Allocator that can be used in C.
///
/// To use this allocator,
/// - Create the allocator in C.
/// - Pass the allocator to the required functions.
///
/// Note: For memory safety, each individual instance of the runtime,
/// and each thread invoked by each instance of the runtime,
/// will require a separate allocator.
const ExperimentalAllocator = extern struct {
context: ?*const anyopaque,
};
pub export fn ExperimentalAllocatorInit() ExperimentalAllocator {
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
const allocator = gpa.allocator();
return .{ .context = &allocator };
}
pub fn ExperimentalAllocatorFree(allocator: *const ExperimentalAllocator, slice: anytype, num_bytes: usize) void {
const a: *const std.mem.Allocator = @ptrCast(@alignCast(allocator.context.?));
const s = @as([*]u8, @ptrCast(slice))[0..num_bytes];
a.free(s);
}
fn dev(allocator: std.mem.Allocator) !void {
const a = ExperimentalAllocator{ .context = &allocator }; // Is this safe?
const slice = try allocator.alloc(u8, 1000);
ExperimentalAllocatorFree(&a, slice, 1000); // < Is this safe?
}
Making it global will solve the issue for now, but will cause more issues in future, as you might already foresee. Especially when we would want to create multiple allocators, in a single application.
What if we did something more sketchy?
The issue is that we can not store a non extern struct inside another extern struct.
So, what if we instead do some bit hacking to copy the bytes of the std.mem.Allocator struct, and store it as a []u8 array?
In C that would be similar to storing an array of bytes as char []. And since it’s just a bunch of bytes, we can store anything in it. Including non extern structs.
This would ensure that we’re only storing the data of std.mem.Allocator and we won’t have to store a bug prone pointer to stack memory.