The problem is in Emulator.init:
pub fn init(program_bytes: []const u8) !Emulator {
// ...
var emu: Emulator = .{
// ...
};
// ...
emu.memory_s = &emu.memory; // points to the current stack frame
// ...
return emu; // this returns a copy of 'emu', including the `emu.memory` array
}
// ...
fn foo() void {
const emu = try Emulator.init(bytes);
// 'emu.memory' resides on the 'foo' stack frame, but
// 'emu.memory_s' points to data on the 'init' stack frame,
// which no longer exists and will be overwritten by junk
}
In summary:
emuis initialized and stored on theinitfunction’s stack frame. This includes theemu.memoryfield (important reminder: arrays are values in zig, they don’t decay into pointers like in C).- A pointer pointing to
emu.memoryis then stored toemu.memory_s, meaning thatemu.memory_sis pointing to the current stack frame. - Finally,
emuis returned to the caller (thefoofunction), meaning that its data is copied by value to the result location. After this, theinitstack frame is no longer active and any data residing on it invalidated. - The
emuinside thefoofunction’smemory_sfield is pointing to data that is no longer valid and which may be overwritten by any random junk data.
I would advice against defining self-referential structs (meaning structs with fields that point to the struct instance itself) as they are very error-prone and easy to make mistakes with when struct instances are copied around without updating pointers.
The memory_s field is pretty redundant; just pass &emu.memory directly instead.
The problem you’re experience is kind of similar to a few recent threads (Seg faults using dir.Walker through interface - #2 by castholm and Local Random Number Generator sometimes core dumps2 - #3 by castholm), so if anything is unclear I would also suggest that you check both of those threads and my answers.