[gclist] Destructor FAQ
Robert A Duff
bobduff@world.std.com
Wed, 13 Mar 1996 10:17:07 -0500
It seems to me that the finalization issue depends somewhat on whether
the language is an "everything in the heap" sort of language. If not,
finalization order can be more well defined.
It seems desirable for finalization order to be at least partly defined.
Consider a long running program that looks something like:
loop -- forever, more or less
Do_Something;
end loop;
And suppose procedure Do_Something opens a file. The file needs to get
closed before the next call to Do_Something, or there will be a "file
handle leak" (which is likely much more severe than a memory leak).
Closing the file needs to be a finalization action, because that's how
you protect against exceptions. Similarly, if finalization is releasing
an expensive operating system resource, you want it to happen fairly
"soon". IMHO, it's no good to say that finalizers are for things that
happen eventually, but you don't really care when.
In Ada, finalization of stack objects happens in reverse order of
initialization, so you can rely on the above file closing to happen.
Finalization of heap objects happens at a point that is based on where
the pointer type is declared -- so if you declare (1) a basic stream,
(2) a pointer type to it, (3) an elaborate stream, and (4) a pointer
type to that, then all elaborate streams will get finalized before all
basic streams (assuming they're all on the heap). The order is
arbitrary for multiple objects of the same type. And finalization is
always done before reclaiming any storage, so finalizers don't run
across dangling pointers. It is possible however, for a finalizer to
run across an already-finalized object (whose storage is not yet
reclaimed). This seems like an inherent problem with finalization -- it
has nothing to do with garbage collection, particularly.
Ada doesn't define the interactions between finalization and gc (neither
does C++, I gather). But clearly it's a non-problem for the stack
objects. For heap objects, the above order makes sense to me. If there
are cycles among objects of different types, the programmer can control
the finalization order by controlling the type declaration order.
Finalization of heap objects is just before program exit, or just before
procedure/block exit, depending on where the pointer type is declared.
I mean, this is the *latest* point -- assuming the garbage collector
hasn't run across the garbage yet at that point (which can happen
because the gc didn't get triggered, or because it's conservative, or
because the implementation doesn't support gc (:-()). And assuming the
program hasn't explicitly deallocated the object.
The programmer also has some control in that a component of an object is
finalized after the object itself. But that makes no sense in a
language where "everything's a pointer".
- Bob