"Valid" content in invalid temporary memory

Hi all,

(Sorry in advance, I’m new to zig and my C is very rusted).

I’ve been experimenting with code snippets from Pointers to Temporary Memory. I’m encountering some surprising results, and I’d appreciate some guidance.

In my code, I’m accessing memory that should be temporary. However, the contents seem to be valid (or at least appear usable) in some cases.

Could someone confirm if this is just a matter of chance (either good or bad luck)? Is it possible that the temporary memory I’m pointing to happens to contain “valid data” by coincidence?

The code at the end of the post returns the following, I would have expected the slice to point to some garbage after some time:

foo aaa u8@7ffc5073dcc8
foo bbbb u8@7ffc5073dcc8
end bbb u8@7ffc5073dcc8
const User = struct {
    domain: []const u8 = undefined,
    enabled: bool = false,

    fn init(domain: []const u8, enabled: bool) User {
        return .{ .domain = domain, .enabled = enabled };
    }

    fn create(
        allocator: Allocator,
        domain: []const u8,
        enabled: bool,
    ) !*User {
        // Allocate uninitialized memory for the User.
        const user_p = try allocator.create(User);

        // Initialize the memory with the init function.
        user_p.* = User.init(domain, enabled);

        return user_p;
    }
};

fn foo(allocator: Allocator, domain: []const u8) !*User {
    var buf: [16]u8 = undefined;
    const len: usize = if (domain.len <= buf.len) domain.len else buf.len;
    const s = buf[0..len];
    @memcpy(s, domain[0..len]);
    log("foo {s} {*}\n", .{ s, s.ptr });
    const user_p = try User.create(allocator, s, true);

    return user_p;
}

pub fn main() !void {
    var logging_alloc = std.heap.loggingAllocator(std.heap.page_allocator);
    const allocator = logging_alloc.allocator();

    const user_p = try foo(allocator, "aaa");
    defer allocator.destroy(user_p);

    var temp: [16]u8 = undefined;
    temp[0] = 10;

    const user2_p = try foo(allocator, "bbbb");
    defer allocator.destroy(user2_p);

    log("end {s} {*}\n", .{ user_p.domain, user_p.domain.ptr });
}

Hi @dattodroid welcome to ziggit :slight_smile:

User.deinit uses non existing self.allocator
This compiles because deinit is not called.

The buf, in stack, lifetime ends in foo. The problem is that user_p.domain points to this memory and the result is undefined.
If you call something else before log("end... that have lot of parameters, arguments or other calls, stack memory is reused and the contents of buf address that user_p.domain holds becomes corrupted.

What’s interesting in this case, however, is that I did try to run it with 7-8 creates and then print them sequentially - they all printed correctly. However, when returning the buffer directly from foo, it immediately printed corrupted memory. I looked at the disassembly and didn’t see anything weird, so I can see why this one is confusing.

I’d have to experiment with it more, but I’m under the impression that we’re getting very lucky here.

2 Likes

I don’t always “deinit” in this specific case:
I call an external program with a child process… wait
the program runs and communicates via a data area,
of course during calculation allocation and free go hand in hand.
but there’s a part that dies with the called program.

1 Like