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