Understanding memory allocation

Hello,

I just started learning Zig allocations, and not understanding what the runtime is telling me. I get a memory leak but the code should deallocate the allocator, and free the char_buffer when leaving main?

If I am not using allocation correctly when converting between pointers, what are rules of thumb to follow when doing so from memory allocation perspective?

pub fn main() !void {

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    var char_buf = try allocator.alloc(u8, 64);
    defer allocator.free(char_buf);

    // *const [5:0]u8 to []u8
    char_buf = try allocator.dupe(u8, "hello");
    std.log.info("Char buf is: {s}", .{char_buf});
}

$ zig run main.zig 
info: Char buf is: hello
error(gpa): memory address 0x720464ec3000 leaked: 
/home/dev/Code/Zig/experiments/yt-beginner/main.zig:14:39: 0x1037b45 in main (main)
    var char_buf = try allocator.alloc(u8, 64);
                                      ^
/usr/lib/zig/std/start.zig:524:37: 0x1037a25 in posixCallMainAndExit (main)
            const result = root.main() catch |err| {
                                    ^
/usr/lib/zig/std/start.zig:266:5: 0x1037541 in _start (main)
    asm volatile (switch (native_arch) {
    ^

Thank you!

1 Like

duped memory also needs to be freed

Basically you are allocating twice here. If you remove the first allocation (which is currently discarded by overwriting it with the dupe result) it should work.

3 Likes

Let’s look in Zig’s source:
/// Copies m to newly allocated memory. Caller owns the memory.

That means, when you call allocator.dupe you create new memory and you have to free this memory.

In your case you have to change two things:

  1. const char_buf = try allocator.alloc(u8, 64); so you get only the address to your buffer and it is fix.

  2. std.mem.copyForwards(u8, char_buf, "hello"); where you copy “hello” to the buffer.

I hope I was able to explain it clearly.

And welcome to the forum!

2 Likes

Oh… I was confused by Copies m to newly allocated memory. Caller owns the memory :wink: I thought it applied to char_buf which was rightfully managed by me: new_buf is char_buf

/// Copies `m` to newly allocated memory. Caller owns the memory.
pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
    const new_buf = try allocator.alloc(T, m.len);
    @memcpy(new_buf, m);
    return new_buf;
}

Oh, you are saying:

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    // *const [5:0]u8 to []u8
    const char_buf = try allocator.dupe(u8, "hello");
    defer allocator.free(char_buf);
    std.log.info("Char buf is: {s}", .{char_buf});

ty

yes, that’s right.

1 Like

I got it - the previous char_buf from const char_buf = try allocator.alloc(u8, 64); was actually superfluous in my case.

Correct. If you want to put different things into your buffer, you have to declare it separately big enough and then put things in step by step.

1 Like

If you just check the pointer addresses both times, you’ll see that they’re different. What’s happening is that you’re overwriting the first char_buf address with a new address from the dupe call.

const std = @import("std");

test "allocations" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    var char_buf = try allocator.alloc(u8, 64);
    std.debug.print("{*}\n", .{char_buf});
    defer allocator.free(char_buf);

    char_buf = try allocator.dupe(u8, "hello");
    std.debug.print("{*}\n", .{char_buf});
}
// My results
// || 1/1 test1.test.allocations...u8@10f928000                                                                                                          
// || u8@10f92d000                                                                                                                                       
// || [gpa] (err): memory address 0x10f928000 leaked:...
1 Like

Yes, I incorrectly thought that the char_buf pointer did not change, and contents dupe new_buf will be assigned to the char_buf pointer ,and so then allocator.free(char_buf) will free the memory. But I see now that I have created a dangling memory with the first allocation, and that’s what the gpa was complaining about.