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:
emu
is initialized and stored on theinit
function’s stack frame. This includes theemu.memory
field (important reminder: arrays are values in zig, they don’t decay into pointers like in C).- A pointer pointing to
emu.memory
is then stored toemu.memory_s
, meaning thatemu.memory_s
is pointing to the current stack frame. - Finally,
emu
is returned to the caller (thefoo
function), meaning that its data is copied by value to the result location. After this, theinit
stack frame is no longer active and any data residing on it invalidated. - The
emu
inside thefoo
function’smemory_s
field 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.