feature. See also
. The project being documented here (as the example) is the Zig library itself.
heap.c_allocator_impl
const c_allocator_impl = struct
File
Code
const c_allocator_impl = struct {
comptime {
if (!builtin.link_libc) {
@compileError("C allocator is only available when linking against libc");
}
}
const vtable: Allocator.VTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
const have_posix_memalign = switch (builtin.os.tag) {
.dragonfly,
.netbsd,
.freebsd,
.illumos,
.openbsd,
.linux,
.driverkit,
.ios,
.maccatalyst,
.macos,
.tvos,
.visionos,
.watchos,
.serenity,
=> true,
else => false,
};
fn allocStrat(need_align: Alignment) union(enum) {
raw,
posix_memalign: if (have_posix_memalign) void else noreturn,
manual_align: if (have_posix_memalign) noreturn else void,
} {
if (Alignment.compare(need_align, .lte, .of(c.max_align_t))) {
return .raw;
}
return if (have_posix_memalign) .posix_memalign else .manual_align;
}
fn manualAlignHeader(aligned_ptr: [*]u8) *[*]u8 {
return @ptrCast(@alignCast(aligned_ptr - @sizeOf(usize)));
}
fn alloc(
_: *anyopaque,
len: usize,
alignment: Alignment,
return_address: usize,
) ?[*]u8 {
_ = return_address;
assert(len > 0);
switch (allocStrat(alignment)) {
.raw => {
// every C type with alignment `max_align_t`, the allocation can be less-aligned.
// The implementation need only guarantee that any type of length `len` would be
// suitably aligned.
//
// For instance, if `len == 8` and `alignment == .@"16"`, then `malloc` may not
// fulfil this request, because there is necessarily no C type with 8-byte size
// but 16-byte alignment.
//
// In theory, the resulting rule here would be target-specific, but in practice,
// the smallest type with an alignment of `max_align_t` has the same size (it's
// usually `c_longdouble`), so we can just extend the allocation size up to the
// alignment of `max_align_t` if necessary.
const actual_len = @max(len, @alignOf(std.c.max_align_t));
const ptr = c.malloc(actual_len) orelse return null;
assert(alignment.check(@intFromPtr(ptr)));
return @ptrCast(ptr);
},
.posix_memalign => {
// multiple of the pointer size
const effective_alignment = @max(alignment.toByteUnits(), @sizeOf(usize));
var aligned_ptr: ?*anyopaque = undefined;
if (c.posix_memalign(&aligned_ptr, effective_alignment, len) != 0) {
return null;
}
assert(alignment.check(@intFromPtr(aligned_ptr)));
return @ptrCast(aligned_ptr);
},
.manual_align => {
// returned by `malloc` before the aligned address.
const padded_len = len + @sizeOf(usize) + alignment.toByteUnits() - 1;
const unaligned_ptr: [*]u8 = @ptrCast(c.malloc(padded_len) orelse return null);
const unaligned_addr = @intFromPtr(unaligned_ptr);
const aligned_addr = alignment.forward(unaligned_addr + @sizeOf(usize));
const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr);
manualAlignHeader(aligned_ptr).* = unaligned_ptr;
return aligned_ptr;
},
}
}
fn resize(
_: *anyopaque,
memory: []u8,
alignment: Alignment,
new_len: usize,
return_address: usize,
) bool {
_ = return_address;
assert(new_len > 0);
if (new_len <= memory.len) {
return true;
}
const mallocSize = func: {
if (@TypeOf(c.malloc_size) != void) break :func c.malloc_size;
if (@TypeOf(c.malloc_usable_size) != void) break :func c.malloc_usable_size;
if (@TypeOf(c._msize) != void) break :func c._msize;
return false;
};
const usable_len: usize = switch (allocStrat(alignment)) {
.raw, .posix_memalign => mallocSize(memory.ptr),
.manual_align => usable_len: {
const unaligned_ptr = manualAlignHeader(memory.ptr).*;
const full_len = mallocSize(unaligned_ptr);
const padding = @intFromPtr(memory.ptr) - @intFromPtr(unaligned_ptr);
break :usable_len full_len - padding;
},
};
return new_len <= usable_len;
}
fn remap(
ctx: *anyopaque,
memory: []u8,
alignment: Alignment,
new_len: usize,
return_address: usize,
) ?[*]u8 {
assert(new_len > 0);
if (resize(ctx, memory, alignment, new_len, return_address)) {
return memory.ptr;
}
switch (allocStrat(alignment)) {
.raw => {
// C only needs to respect `max_align_t` up to the allocation size due to object
// alignment rules. If necessary, extend the allocation size.
const actual_len = @max(new_len, @alignOf(std.c.max_align_t));
const new_ptr = c.realloc(memory.ptr, actual_len) orelse return null;
assert(alignment.check(@intFromPtr(new_ptr)));
return @ptrCast(new_ptr);
},
.posix_memalign, .manual_align => {
// the original alignment, so we can't do anything more.
return null;
},
}
}
fn free(
_: *anyopaque,
memory: []u8,
alignment: Alignment,
return_address: usize,
) void {
_ = return_address;
switch (allocStrat(alignment)) {
.raw, .posix_memalign => c.free(memory.ptr),
.manual_align => c.free(manualAlignHeader(memory.ptr).*),
}
}
}