[gclist] Destructor FAQ

boehm.PARC@xerox.com boehm.PARC@xerox.com
Fri, 15 Mar 1996 16:11:38 PST


Bob Duff writes:

"There's a big difference between writing to a closed file, versus
following a dangling pointer.  The former has well-defined semantics
(raises an exception or whatever).  The latter can do arbitrary damage,
and can behave differently from one system to the next, or even from one
run of the program to the next."

But that's an artifact of the example.  Probably a more common use of
finalization is in a wrapper object W for an object T managed by a third-party
C library.  When all references to W are dropped, W's finalizer calls free(T).
If the implementor of W isn't careful, you again have a dangling pointer.
(Things are a little better than in the purely explicitly managed world, in
that if W's implementor is careful, the reference to T can be cleared.  Thus
you might be able to generate a trap when you access the finalized object.  But
the difference seems minor.)

Even worse, if a finalizer decides to recycle some of the data structure for
another client, you are again in a very similar situation to the dangling
pointer case.

"But all these horrible problems everybody is worried about *only* occur
when you have cyclic dependencies.  I can't imagine why the file points
to the buffered-file.  The latter abstraction is built on top of the
former, and simple rules can choose the "right" order of finalization."

I agree.  The important thing is to handle the acyclic case well, keeping in
mind that cycles can occur accidentally.

As you suggest, you can avoid the problem with cycles by imposing a
finalization ordering based on types and not reference patterns.  I personally
prefer that to an undefined ordering.  But it still forces me to violate
abstractions in simple cases where topological ordering (or equivalently
post-mortem finalization) would just do the right thing.

Expanding on the file example a bit, consider the case in which the unbuffered
file is actually a frob, which is implemented by a foo, which has a finalizer.
As the implementer of the buffered stream abstraction with static finalization
order, I now need to make sure that I get the right finalization ordering with
respect to a foo, which is implemented in some other module which I shouldn't
know about.  If I use topological ordering, the garbage collector will simply
conclude that the unbuffered file depends on the foo, and finalize the
unbuffered file first.

Hans