What should happen here?
jfondren
julian.fondren at gmail.com
Wed Sep 22 20:17:48 UTC 2021
On Wednesday, 22 September 2021 at 19:30:37 UTC, Paul Backus
wrote:
> Either (a) the compiler must assume, pessimistically, that a
> pointer passed to a function may be stored somewhere the GC
> can't see it, and therefore must be preserved in local storage
> until the end of its lexical lifetime, or (b) the user must
> manually signal to the compiler or the GC that the pointer
> should be kept alive before passing it to the function.
>
> Given how error-prone option (b) is, I think (a) is the more
> sensible choice. Users who don't want the pointer kept alive
> can always opt-in to that behavior by enclosing it in a block
> scope to limit its lifetime, or setting it to `null` when
> they're done with it.
Neither of these solutions would've helped with the original code
that used the epoll API.
The pointer passed to C wasn't important, and it would've been
fine for its lifetime to end at the end of the function (it was
even a pointer to a stack-allocated struct in the function: epoll
copies the struct out of the pointer given to it).
The pointer that mattered was a misaligned class reference in the
structure passed to C. This pointer was to an object that will
eventually get destructed before the end of its function scope.
*That* objected wasn't passed anywhere in its scope, it was
initialized and then had a method called on it.
At the site of the short-lived object, there's nothing apparently
wrong. At the site of the C API call, what can you do? If you
tell the GC that the short-lived object's reference is important,
the GC still can't see any references to it afterward, and the GC
isn't responsible for the object's short lifetime. If you tell
the compiler that the short-lived object's reference is
important, this could be a completely separate compilation and
the decision to expire it early might've already been made.
I think the intuition that's violated here is "the GC is
nondeterministic, but the stack is deterministic." And the
correct intuition is "class destruction is nondeterministic, but
struct destruction is (usually) deterministic."
This also can happen without using calling out to C:
```d
import std.stdio : writeln;
import core.memory : GC;
struct Hidden {
align(1):
int spacer;
C obj;
}
Hidden hidden;
class C {
int id;
this(int id) {
this.id = id;
hidden.obj = this;
}
~this() {
id = -id;
writeln("dtor");
}
}
void check() {
writeln(hidden.obj.id);
}
void main() {
auto c = new C(17);
check; // 17, c is alive
GC.collect;
GC.collect;
check; // -17, c was destroyed
writeln("here");
}
```
More information about the Digitalmars-d
mailing list