[comp.object] copy collecting GCs and destructors?

tom@ssd.csd.harris.com (Tom Horsley) (05/15/91)

In a generation scavenging garbage collector (or I suppose any other form of
copying collector) you never explicitly "delete" an object in memory, you
just fail to copy it to the new memory area.

With a garbage collector that works like this, how do you go about
implementing "destructor" calls (to use C++ terminology). Just as one
example of why you might want a destructor: you could have a stream I/O
object connected to some low level operating system file descriptor, when
the I/O object is no longer referenced, you would like to close the file
descriptor.

I have read quite a few papers on garbage collection, but I don't recall
ever seeing this issue addressed. I have though of a few ways to implement
destructors, but I am wondering if any existing systems have clever
algorithms to solve this problem. Does anyone know of any papers that
describe good ways to do this, or any existing systems that do this?
--
======================================================================
domain: tahorsley@csd.harris.com       USMail: Tom Horsley
  uucp: ...!uunet!hcx1!tahorsley               511 Kingbird Circle
                                               Delray Beach, FL  33444
+==== Censorship is the only form of Obscenity ======================+
|     (Wait, I forgot government tobacco subsidies...)               |
+====================================================================+

kend@data.UUCP (Ken Dickey) (05/16/91)

tom@ssd.csd.harris.com (Tom Horsley) writes:

>In a generation scavenging garbage collector (or I suppose any other form of
>copying collector) you never explicitly "delete" an object in memory, you
>just fail to copy it to the new memory area.

>With a garbage collector that works like this, how do you go about
>implementing "destructor" calls (to use C++ terminology). 

Closing ports (files) and making dumb storage allocators clean up
after themselves is done either by implementing a finalization service
(i.e. registering objects which need finalization) or by using an
implementation which does this for you.

The basic strategy is to have a "post-gc-deamon" which gets called
just after a collection occurs and which checks for (and "finalizes")
any newly dead objects.  A general strategy is to use "weak" pointers
and then check after gc for the broken ones (the ones which did not
survive).  There are a number of Lisp collectors which implement weak
pointers, weak pairs, populations, etc. {E.g. look at the T
implementation from YALE}.

One reference for weak pointers (copying collector implementation) is
in Jim Miller's PhD thesis: "MultiScheme", MIT/LCS/TR-402, MIT,
September 1987.


-Ken Dickey				kend@data.uucp

dlw@odi.com (Dan Weinreb) (05/20/91)

I worked for about ten years developing and maintaining system
software in Lisp, in which all deallocation was done automatically by
a garbage collector.  Objects had the equivalent of C++ constructors
(called the ":init" method), but there was no equivalent of C++
destructors.  (We didn't make any significant use of the "weak link",
"post-gc-action" sort of thing that was mentioned in an earlier reply
to your posting.)

Generally, I don't think it would have been terribly useful to have a
feature that would run a method when an object was collected by the
GC, for several reasons:

(1) If the object contained references to other objects, those other
objects could be garbage too, and might no longer exist.  So such a
method would be exceedingly unsafe.

(2) There's no telling when the GC will actually run.  It might not be
for hours, in principle.  So you'd never know when the action would
happen.

Nowadays I program in C++, so I can compare the techiques used in the
two languages.  Consider an automatic object in C++.  The destructor
is run when the block is exited, either normally or because an
exception unwinds the block.  (Yes, C++ exceptions aren't in the
language yet, but they will be.)  In Lisp, there is a construct called
unwind-protect that lets you set up the same kind of control
structure.  Those actions that might be placed in a C++ destructor
would often correspond, in Lisp, to actions in the handler part of an
unwind-protect.  An unwind-protect handler might send a message to an
object (a.k.a. call a generic function on an object).  Sometimes the
unwind-protect with this handler would be packaged up in a Lisp macro.
A typical such macro, part of the built-in system, is "with-stream",
which opens a stream, executes a body, and then (in the unwind-protect
handler) closes the stream.

For heap-allocated objects, there isn't anything in Lisp that
corresponds to the C++ destructor.  Instead, where a C++ programmer
would use a "delete" statement, the Lisp programmer would just send
some message (a.k.a. call some generic function) that would perform
actions analogous to those in the C++ destructor.