#Temporary lifetime
1 messages · Page 1 of 1 (latest)
temporary variables are allocated on the stack of the caller
But how long is that stack space valid? If I do foo(&.{a}) + foo(&.{b}), can the second tuple clobber the first? What about foo(&.{a}); foo(&.{b});?
the stack space is valid until the function calling foo returns
Got it thanks
Wait that can't be right
while (true) foo(&.{a}); would require infinite stack space
any stack variables in a loop are allowed to be reused, otherwise all loops would require infinite stack space
the stack space still lives until the end of the function scope
note: the function scope refers to the called function
the array lives until the end of scope, not until the function returns.
So with while (true) { foo(&.{a}); foo(&.{b}); } foo(a) is guaranteed to remain valid during foo(b), and the optimizer is not allowed to reuse the memory?
Then at the closing brace, both arrays become invalid?
are you storing &.{a} inside the foo function?
More or less yeah. But only if I find out it's legal lol
the optimizer will not reuse the memory in that case.
'cause I'm pretty sure &.{a} just becomes invalid after the foo(&.{a}) call ends
sounds like a shaky assumption
It seemed dubious to me, but I'd like to know what guarantees the language spec makes so I can take advantage of them
there likely are none
and/or not overly pessimize my code
i very much doubt these semantics are gonna change
alright, so the optimiser doesn't appear to re-use the addresses here, from what I can tell
But is it guaranteed to stay that way? 🙂
there are no guarantees that I know of
These details are kind of important to pin down if your language doesn't have a borrow checker lol
just to note though:
const std = @import("std");
var a: u8 = 0;
var b: u8 = 0;
var c: u8 = 0;
pub fn main() !void {
foo(&.{a});
{
foo(&.{b});
}
foo(&.{c});
}
noinline fn foo(slice: []const u8) void {
std.debug.print("{x}\n", .{@ptrToInt(slice.ptr)});
}
this prints 3 different addresses, despite the second call being in a scope in which the stack used by the temporary should die
well that the optimizer is allowed to do something doesnt mean it will
a good guarantee is that sometime after calling foo, the temporary will become invalid, but not while calling foo; relying on anything else may break
¯_(ツ)_/¯
at any rate, I concur with this. Relying on any sort of secret temporary stack allocations seems like a bad idea
naah this is probably defined, no?
nothing is defined when there isn't a spec
I agree with the gut feel if I'm being maximally cautious, but it would helpful to know for sure
oh shit the whole language is UB
Are you allowed to ask questions in #compiler-devel?
pointers to temporaries were made const specifically to avoid using them as secret allocations
or are the plebs not allowed in
😦
Taken to the extreme this means in foo(&.{a}).bar(&.{b}), .{a} will be invalid during bar
Right?
yes
That is extremely limiting
a scope-local constant is extremely non-magical
Yeah I don't see how that's magic. The word "magic" is overused in discussions like this. This isn't Ruby
at the moment it's mostly "here's a pointer to something, it'll live for a little bit" unless you're actively paying attention to each and every scope it's not immediately obvious
not magic, but it just doesn't seem like that good of an idea. There's nearly no place in zig where you can just have an implicit memory location that isn't comptime
declaring where your memory lives is literally one of zig's front-facing ideals
it isnt implicit. its local to the block its in, just like any expression. this is perfectly logical.
logical != good
its consistent with how zig does memory.
how?
scope ends with the block (thats also what defer triggers on) and thats also where stack variables are freed
stack variables
and constants
the implicit part here is that there's no variable acting as the memory location
the compiler would just create it for you
and the fact that this thread exists makes it pretty obvious it's not immediately clear whether or not that's what would be done
hmm yea
well i think its good if it stays this way because everything else would make stuff incredibly annoying 😛
I'll be chuckling like a smug evil villain when the optimiser wreaks havoc on you
https://mzte.de/git/LordMZTE/vidzig/src/branch/cursed-frontend/frontend/layout.zig#L51 look at this. imagine this kinda code with this change.
also DO NOT question what this is, very janky experimental stuff, dont ask lol
I can imagine. I imagine it would force you to take a different approach lol
yea i should anyways
I actually find this behavior this incredibly intuitive but it is also behavior that is full of footguns in both C and C++
I don't think it's inherently unintuitive it's just breaking the rules of the predecessors
if I were writing assembly and needed the address of a temporary I obviously have to put it on the stack briefly
so zig having the same behavior is like duh
C++'s rules are dumb as fuck to me but I get that RAII makes it complicated
this assumes knowledge of assembly
sure, I'd make no argument about anything being inherent. But by virtue of there being anything to discuss about it means there can't really be a consensus on how intuitive it is - meaning, it will make sense if you already know how it works
zig maps very close to assembly, and yes in zig you are expected to have an understanding of the stack, heap, static vars which are all low-level concepts
I mean, from that perspective, it makes more sense why you'd need to allocate a variable explicitly
like you said, you have to put it on the stack
yeah, there's no other obvious alternative
besides making it UB or forbidden
so why not just implement the obvious behavior
I feel like that's intuitive
on the other hand, people often create const variables which get compiled away and never actually exist on the stack. is that unintuitive? I don't really think so
that doesn't result in any observably different behaviour, unless you're working directly with the stack pointer or whatever else
exactly
yeah I don't think it necessarily matters if it's intuitive, it just needs to be defined by the language, so people can learn it once and know what is/isn't allowed. as of now the consensus on the rules is "nobody knows". and the pre-1.0 compiler does little if any stack packing, and so people are taking advantage of current observed behavior. but there's a looming threat that one day an optimization will silently break your code if you run afoul of some undecided future set of rules. that doesn't seem like a great situation.
here's an idea. in debug builds, the language should overwrite temporaries with 0xaa once they're no longer valid. that way mistakes are guaranteed to be noticed no matter what the optimizer decides to do with the stack
but maybe that's more of a #bikeshed than a #1019652020308824145