Struct member value change erratically

I have something very funny going on, which, really bowls me over:

The crux is that:

    const b = font.getChar('j');
    std.debug.print("datalen={}, height={}, width={}\n", .{b.data.len, b.size.height(), b.size.width()});
    std.debug.print("datalen={}, height={}, width={}\n", .{b.data.len, b.size.height(), b.size.width()});

Prints:

datalen=462, height=33, width=14
datalen=462, height=-1, width=-17079488

The b.size (Size struct) does not mutate the data in width()/height() methods, yet, as you can see from the output, it goes horribly wrong somewhere.

    const Size = struct {
        m_x : c_int = undefined,
        m_x2 : c_int = undefined,
        m_y : c_int = undefined,
        m_y2 : c_int = undefined,
        m_offset: usize = undefined,

        pub fn width(self: @This()) c_int {
            return self.m_x2 - self.m_x;
        }

        pub fn height(self: @This()) c_int {
            return self.m_y2 - self.m_y;
        }

        pub fn getBufferSize(self: @This()) usize {
            return @intCast(self.width() * self.height());
        }
    }

The method getChar, not that I think it is too relevant, returns a struct to the character size and the underlying bitmap:


    pub fn getChar(
        self: @This(),
        character: usize,
    ) struct {
        data: []u8,
        size : *Size,
    } {
        assert(character < NO_CHARS);
        assert(self.m_buffer != null);

This is done using Zig 0.14.1. It is a single threaded simple/straight forward test.

If I change the getChar result to:

    pub fn getChar(
        self: @This(),
        character: usize,
    ) struct {
        data: []u8,
        size : Size,
    } {

Then I get a consistent answer:

datalen=462, height=33, width=14
datalen=462, height=33, width=14

But why? How where/how is the pointer changed or the values of the struct? It is obvious that I am missing something fundamental, as in my opinion neither the pointer, nor the value should change between the two prints.

you didn’t post the full getChar function, so I can’t be positive, but looking at the signature I’m guessing that you are returning a pointer to a stack allocated variable.

4 Likes

when you pass a pointer to a stack allocated object up the call stack, it’s data will be wiped when you make another function call, the new function will wipe your data.

1 Like

Any time you return a reference (e.g. a pointer or a slice) instead of a value should ring the alarm bells in your head and make you double check where (or rather: how long) the pointed-to data actually lives, because if it is on the stack (and thus doesn’t outlive the function call) Zig won’t yell at you and you end up with a dangling pointer in the result.

…the reason why your first print looks correct is purely accidential because the pointed-to data is still present on the stack (but in the invalid area beyond the stack pointer).

IMHO this is currently the biggest problem that Zig needs to address because it is a very common footgun (considering how often the issue pops up here), and even C compilers have started to warn about returning stack addresses at least in simple situations (unfortunately neither Clang nor GCC would warn in your specific case of returning the bad pointer when it’s wrapped in a struct value so I can’t smuggly point to C being superior to Zig lol).

5 Likes

@floooh - thank you. You are 100% correct…

The root of this problem was that I mean to call with

*This()

instead of

This()

meaning the one makes a copy, the other one points… big thank you again - I understand in concept what is going wrong, just not perfectly sure how that applies to something that should seemingly be consistent on the between 2 calls… will have a look at your comment in detail shortly when I get back to the code.

Big thanks again.

Thanks for your reply. You are correct. As stated, I missed a *… and the ‘incidental’ really threw me