Creating a pointer in another scope gets overwritten - How does this happen?

Hiyo,

Sorry about the title, I couldn’t really explain what was going on without showing some code:

const std = @import("std");
const builtin = @import("builtin");

pub fn main() !void {
    const p = init_parent();
    std.debug.print("MAIN ID: {d} CHILD ID: {d}\n", .{p.id, p.child.parent_ptr.id});
    std.debug.print("MAIN ID: {d} CHILD ID: {d}\n", .{p.id, p.child.parent_ptr.id});
    std.debug.print("MAIN ID: {d} CHILD ID: {d}\n", .{p.id, p.child.parent_ptr.id});
}

fn init_parent() Parent {
    var p = Parent {
    	.id = 1,
        .child = undefined
    };
    
    p.child = init_child(&p);
    return p;
}

fn init_child(parent_ptr: *Parent) Child {
    const c = Child {
        .cid = 2,
    	.parent_ptr = parent_ptr
    };
    return c;
}

const Parent = struct {
    id: i32,
    child: Child
};

const Child = struct {
    cid: i32,
    parent_ptr: *Parent 
};

When I run this code, for the first output it prints MAIN PID: 1 CHILD PID: 1 which is what’s expected but the next outputs’ MAIN PID keeps its value of 1, while CHILD PID gives me gibberish.

Please could someone help me out with this? how can I be able to access the parent’s id from the pointer?

Thanks :slight_smile:

i’m not sure why the first run works while the others do not, but the core problem here is that p is being copied/moved after being initialized, since you’re returning Parent by value. if you need a stable pointer for p for init_child(), you’ll either need to create it on the heap, or refactor init_parent() to take in *Parent by pointer

What’s happening here is that you’re taking a pointer to a local variable, and returning it. This is not allowed, because p is created on the stack of init_parent, and copied to main.

Here’s what the stack roughly looks like when you set the parent_ptr in init_child:

--- end of stack ---
c: Child @3
--- init_child(@2) locals above here ---
p: Parent @2
--- init_parent() locals above here ---
p: Parent @1
--- main() locals above here ---

You’re setting c.parent_ptr to @2 here. When init_parent returns, @2 no longer contains any useful data, because its stack space is now free for std.debug.print to use:

... stack continues with functions called by std.debug.print
stderr: std.io.Writer(...) @2
--- std.debug.print("MAIN ID"...) locals above here ---
p: Parent @1
--- main() locals above here ---

What you should be doing is allocating the parent on the stack of main only, and passing a pointer to that to init_parent. That way, p stays alive for the entire time you need it:

pub fn main() !void {
    var p: Parent = undefined;
    init_parent(&p);
    std.debug.print("MAIN ID: {d} CHILD ID: {d}\n", .{p.id, p.child.parent_ptr.id});
    std.debug.print("MAIN ID: {d} CHILD ID: {d}\n", .{p.id, p.child.parent_ptr.id});
    std.debug.print("MAIN ID: {d} CHILD ID: {d}\n", .{p.id, p.child.parent_ptr.id});
}

fn init_parent(p: *Parent) void {
    p.* = Parent {
    	.id = 1,
        .child = undefined
    };
    
    p.child = init_child(p);
}

That way, you are setting parent_ptr to @1, which is alive as long as main is.

2 Likes