Slice and Array not pointing at the same data

I’ve run into a rather confusing issue where an array and it’s respective slice seem to be pointing at two different things. I am writting a CHIP-8 emulator and I was trying to do some conversion from u8 array to u16 array, in the middle I decided to use a slice rather than an array but started getting errors through the code so I slipped in a few asserts

Here is the assert line:

    const upper: u16 = self.memory_s[self.execute_cursor];
    const lower: u16 = self.memory_s[self.execute_cursor + 1];

    std.log.info("{} {}", .{ self.execute_cursor, self.execute_cursor + 1 });
    std.debug.assert(self.memory[self.execute_cursor] == self.memory_s[self.execute_cursor]);
    std.debug.assert(self.memory[self.execute_cursor + 1] == self.memory_s[self.execute_cursor + 1]);

The memory_s is assigned to on init and then never changed again

This is the error

C:\Programs\zig\zig-windows-x86_64-0.14.0-dev.3062+ff551374a\lib\std\debug.zig:518:14: 0x7ff79d3b71ed in assert (Chip8.exe.obj)
    if (!ok) unreachable; // assertion failure
             ^
D:\Coding\Chip8\src\emulator.zig:389:21: 0x7ff79d3b57f7 in currentInstruction (Chip8.exe.obj)
    std.debug.assert(self.memory[self.execute_cursor] == self.memory_s[self.execute_cursor]);

The repo in question in the specidied branch oddError:

At first I believed it’s just another mess up on my part so I went around the code trying to ensure that It’s not broken. But the pointer address of the slice not being the same as the array while debugging had me thinking this could be compiler related. So to be on the safe side, I decided to mention it here first.

Zig version: 0.14.0-dev.3062+ff551374a

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 the init function’s stack frame. This includes the emu.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 to emu.memory_s, meaning that emu.memory_s is pointing to the current stack frame.
  • Finally, emu is returned to the caller (the foo function), meaning that its data is copied by value to the result location. After this, the init stack frame is no longer active and any data residing on it invalidated.
  • The emu inside the foo function’s memory_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.

3 Likes

I must admit, I fully assumed that returned memory would be at the same address as if this was heap allocated. Thanks a lot!

The reason I was doing this was to test for why the u16 pointer wasn’t behaving as expected.

That’s one more thing for me to keep in mind.

Again, thanks a lot!

Relevant issues/proposals that might have saved you from the confusion if implemented :slightly_smiling_face::

2 Likes