Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.2 9/17/84; site think.ARPA Path: utzoo!watmath!clyde!burl!ulysses!allegra!mit-eddie!think!rose From: rose@think.ARPA (John Rose) Newsgroups: net.lang.c++ Subject: Re: Exception handling in C++ Message-ID: <4431@think.ARPA> Date: Wed, 26-Feb-86 17:06:53 EST Article-I.D.: think.4431 Posted: Wed Feb 26 17:06:53 1986 Date-Received: Fri, 28-Feb-86 21:57:11 EST References: <51@cecil.UUCP> Reply-To: rose@think.UUCP (John Rose) Organization: Thinking Machines, Cambridge, MA Lines: 172 Keywords: exceptions, unwind-protect (Note: I can't tell if I'm getting onto the net. No one commented on my previous posting on lambda expressions and expression-valued blocks in C (net.lang.c), so I couldn't tell if I'd gotten through. If there is no comment on this paper too, could some kind moderator-type please e-mail me a pat on the back saying it was at least broadcasted?) > 1. Will this implementation work in general, or have I forgotten > something? Are there some dire consequences of using setjmp/longjmp in > this fashion? Note that destructors will not be called when a block is ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > exited by doing a RAISE. This is a bug in longjmp() which is easily fixed with a little conspiracy with the C++ compiler. A guarantee of de-initialization which only applies to local exits is not much of a guarantee. For example, I would like to write an open-file abstraction which ensures a close at the end of its use. I use a stdio FILE instead of a stream to make explicit the resource management: class open_file { FILE* fp; public: open_file(char *name, char *mode = "r") { fp = fopen(name, mode); } ~open_file() { fclose(fp); } operator FILE*() { return fp; } }; void load_file(char *name) { open_file in(name); // do something with the stream, e.g.: extern lispval read(FILE *), eval(lispval); lispval l; while (l = read(in)) (void)eval(l); // ...and when open_file goes out of scope, the right things happen } Implementationally, an "open_file" is just a FILE pointer, with guaranteed de-initialization tacked on. The guarantee should have the force of a Lisp UNWIND-PROTECT, or else the open_file abstraction is much less usable. And if we don't have a garbage collector to manage storage behind our backs, free store management requires such guarantees also. (What other examples of guaranteed deinitialization are there? Do they require UNWIND-PROTECTs? And is the use of non-local exits limited to a few types of application, such as user interfaces and language interpreters? Can you give an example of an application where non-local exits are inconceivable, or where we don't care about non-local deinitialization?) Here is some C code which would be a plausible translation of the above, including UNWIND-PROTECTs. You may have seen this technique in Betz's XLISP, which is written in C. /* #include : */ struct catch { void (*catch_fn)(); void *catch_arg; struct catch *catch_prev; }; extern struct catch _thread; /* end include */ struct open_file { FILE* fp; }; void deinit_open_file(this) void *this; { fclose(((struct open_file *)this)->fp); } void load_file(name) char *name; { struct open_file in; extern lispval read(), eval(); lispval l; // Establish a catch block: struct catch deinit_in; deinit_in.catch_fn = deinit_open_file; deinit_in.catch_arg = (void *)∈ deinit_in.catch_prev = _thread; _thread = &deinit_in; in.fp = fopen(name, "r"); while (l = read(in.fp)) (void)eval(l); _thread = _thread->catch_prev; fclose(in.fp); // inline exp. of deinit_open_file } Clearly, there is an expense in establishing the catch block: Four extra accesses to a new structure on the stack, and two accesses to an external variable are needed for each creation/deletion of a protected object; an out-of-line delete function must also be compiled. But this expense is not incurred for classes without guaranteed deinitialization, and perhaps it is reasonable to declare that any class which needs guaranteed deinit. should need it consistently for non-local exits. Also, particular architectures such as the Vax allow cheap establishment of unwind handlers (one write to the stack frame). A compromise: After the signature is an outline of a library class which provides unwind protection, and some applications, including file closing. The idea is that objects of class "catch" are ALWAYS deinitialized, even by longjmp(), and so if you include such an object as one of your members, or as a local variable, you can get a hook on stack unwinds. ---------------------------------------------------------- John R. Rose, Thinking Machines Corporation, Cambridge, MA 245 First St., Cambridge, MA 02142 (617) 876-1111 X270 rose@think.arpa ihnp4!think!rose class catch { static catch* thread = 0; catch* prev; virtual void spring() { /* your code here */ } friend void _unwind_catches(void *); public: catch() { prev = thread; thread = this; auto_check(this); } ~catch() { thread = prev; spring(); } }; void _unwind_catches(void *upto) // Normally only called on { for (catch *cur; (void *)(cur = thread) < upto; ) { thread = cur->prev; // same as ~catch(). cur->spring(); } } /* Just before longjmp unwinds each stack frame, * it calls _unwind_catches() with the address of that frame. */ class generic_catch : catch { void (*catch_fn)(); void *catch_arg; void spring() { catch_fn(catch_arg); } public: generic_catch(void (*fn)(), void *arg = 0) { catch_fn = fn; catch_arg = arg; } } class catch_restart : catch { jmp_buf from_the_top; // HACK! To jump out of the unwind. inline catch_restart() { flag = setjmp(from_the_top); } virtual int restart_p() { return 1; } // Predicate for when to restart. void spring() { if (restart_p()) longjmp(from_the_top, 1); } public: int flag; } // Here is class open_file: class open_file { friend file_catch; class file_catch : catch { void spring() // depends on being 1st in struct: { fclose(((open_file *)this)->fp); } } cb; FILE* fp; public: open_file(char *name, char *mode = "r"); // No destructor: handled by member cb. }; -- ---------------------------------------------------------- John R. Rose, Thinking Machines Corporation, Cambridge, MA 245 First St., Cambridge, MA 02142 (617) 876-1111 X270 rose@think.arpa ihnp4!think!rose