Path: utzoo!utgpu!watmath!clyde!att!osu-cis!tut.cis.ohio-state.edu!bloom-beacon!gatech!hubcap!ncrcae!ncrlnk!uunet!mcvax!ukc!dcl-cs!aber-cs!pcg From: pcg@aber-cs.UUCP (Piercarlo Grandi) Newsgroups: comp.lang.c++ Subject: Re: goodbye cpp ??? (macros vs. inline functions) Message-ID: <425@aber-cs.UUCP> Date: 17 Dec 88 22:05:02 GMT Reply-To: pcg@cs.aber.ac.uk (Piercarlo Grandi) Distribution: eunet,world Organization: CS Dept., University College of Wales, Aberystwyth, UK (Disclaimer: my statements are purely personal) Lines: 672 In article <11001@ulysses.homer.nj.att.com> jss@hector.UUCP (Jerry Schwarz) writes: Also note, that setjmp/longjmp must be used very carefully (if at all) in C++ programs because destructors are not called when longjmp unwinds the stack. Old problem to Lispers; in sophisticated Lisps, catch/throw (essentially equivalent to setjmp/longjmp as means of transferring control non locally) trigger entry/exit hooks associated to each scope they fly over. Note that in the general case you do not just want exit hooks when leaving a block, you also want entry hooks when you enter a block. In sophisticated languages, a block instance may be exited/reentered several times... In a very real sense the set of constructors for a block in C++ is the entry hook, and the destructors are the exit hook. The problem is that it requires new syntax and some hazy code to generalize this to non local transfers of control. As a contribution to the discussion on constructors/destructors etc..., I repost a couple of library components that did not make it out of Europe when I had posted them. They are both examples of how to use the constructor/destructor mechanism as generalized entry/exit hooks, to implement shallow bound dynamic scoping of variables, and tracing of procedure calls. Here it goes: =============================================================================== In article <4370@polya.Stanford.EDU> shap@polya.Stanford.EDU (Jonathan S. Shapiro) writes: There is at least one way to do exception handling in the current scheme of things. It goes like this: [ complicated things deleted... ] Now I know that there are lots and lots of problems here. I also know that I don't know a lot about exception handling. Dear Jonathan S. Shapiro at least you hit upon the obviously right idea soon enough, something that most people that have dealt with exception handling have not yet done. In an old paper of mine in an obscure italian journal I argued (I hope definitively) that exception handling only requires dynamic scoping; non local control transfers are totally orthogonal to the issue of exception handling. Dynamic scope is useful per se, and in its shallow bound variety can be implemented in a language like Pascal or C in an afternoon (I did that for fun to the PCC years ago...). Non local control transfers require rerooting of the control "tree" (can be a graph) AND the environment "tree" (can be a graph); it also requires rerooting of the various "tree"s maintained by the application. This can only be done by allowing general entry and exit functions. The wrapper feature in G++ is a step in that direction. I am enclosing an ar(1) tail to this message; it contains a couple of goodies, one for defining dynamically scoped esception handlers, another to do nice function tracing. They demonstrate some nice ideas. They are useful but limited; you may think of ways to extend them... I refrained from doing so because I do not like complicated machinery. I will also be posting sooner or later a C++ version of a fully general coroutine/generators package of C macros that I perfected a couple of years. ago. It uses some nifty technique (taking address of labels... Thanks to Steve Johnson for permitting that in PCC and to RMS for permitting that in GCC). You will be fazed. ---------------------- CUT HERE -- AR(1) FORMAT FILE FOLLOWS ---------------- ! Handler.3 591288803 137 20 100600 1844 ` .TH HANDLER 3+ "PCG" .ad .SH NAME handler, handlerin \- redefine for the duration of a block a pointer to proc .SH SYNOPSIS .nf .B "#include " .sp .BI "handler(" procpointer ") = " newvalue ; .BI "handlerin(" class , op , procpointer ") = " newvalue ; .fi .SH DESCRIPTION These two macros, one for a simple variable, the other for a member of a class, allow dynamic redefinition of its value locally to a block. The variable .B must have type pointer to procedure. This is most useful to redefine for the duration of a block an exception handler. .LP There are two forms of the macro, one for straight global variables, and the other one for members of classes. The second form has three parameters, the name of the structure, the sequence used to get to the member (that is the path used to get to the member, a sequence of names, possibly empty, connected by one of .BR "::" ", " "->" ", " "." ), the name of the member. .SH EXAMPLES .nf { handler(SigSegv) = &InvokeDebugger; . . . . } { handlerin(Stack,::,stackIsEmpty) = &PrintErrorMessage; . . . . } .SH BUGS Do not use global variable names prefixed by double colons. .LP The first and third parameters of .B handlerin are used to build a hopefully unique name for a temporary; if two handlers have the same name in the same class but are reached via different paths, such as .B aclass::astruct.handler and .B aclass::bstruct.handler , and theyr are redefined in the same block, a clash will result. This is what you deserve of course. .LP Non local gotos via library procedures will .B not restore the previous value; straight non local gotos or returns out of a block will restore the previous value, as the macros expand to a declaration of a dummy variable whose constructor puts into it the current value of the global, and the destructor restores it. Handler.H 592342047 137 20 100600 3749 ` /* Copyright 1988 Free Software Foundation. All rights reserved. This work of authorship of Piercarlo Grandi is the property of the Free Software Foundation, and its use and reproduction are subject to the terms and conditions detailed in the GNU C++ license. This header allows the local redefinition of a pointer to a function variable. It would be more complicated and inefficient, but possible, to use a similar scheme for implementing dynamic binding on any type of global variable (hint: pointerToHandler must become a void *, and points to a chunk of core of the right size...). We have here only pointers to procedures. This is what we really need most for now, as the ability to have dynamic scope pointer to procedure variables is all one's needs to do exception handling (no, Shaula and Barbara and Jean, non local transfer of control is NOT identical with exception handling, though often an exception handler will do it!). If you are a real sophisticated type you may want then a non local goto implementation that does activate all destructors on exit from a scope and constructors on entry. If you do this you have the means to implement Baker's environment rerooting... (rumour has it that Dr. Stroustrup is haggling with the problem of how to define non local jumps in C++, we all hope he reads Baker's and Steele's classics...). In its limited sophistication this source is just a fix waiting for Saint Michael Tiemann to implement the following extension to C++, that whenever a global is declared in a block as "extern auto", its value is saved on the stack at entry to the block and restored from the stack on exit. This is of course a shallow bound implementation of dynamic scoping... I did this extension to the Vax PCC in an afternoon (note, I have lost the modifications years ago, don't ask me for a copy). The fix is useful though, as tipically the function pointer variable will be used to point to an exception handler, and this macro and type definition allow redefition local to a block of the exception handler. Example: extern void abort(); Handler OnUnderflow = (Handler) &abort; . . . . inline float ReturnZero() { return 0.0; } float SomeFunction ( float anArg ) { handler(OnUnderflow) = (Handler) &ReturnZero; { handler(OnUnderflow) = (Handler) &abort; . . . . } . . . . } */ typedef void (*Handler)(void *); class HandlerFrame { private: Handler *pointerToHandler; Handler previousHandler; public: inline HandlerFrame ( register Handler *pointerToHandler ) { this->pointerToHandler = pointerToHandler; this->previousHandler = *pointerToHandler; } inline void ~HandlerFrame ( ) { *(this->pointerToHandler) = this->previousHandler; } }; /* We need two different macros, one for ordinary global handlers, the other for handlers that are members of classes. In the latter case the user must break up the reference to the member in three parts, the naem of class, operator used to get to the member (:: -> .), and the name of member. Note that we MUST put in underscores; think of classes TreeBig with member Apple and class Tree with member BigApple... */ #define handler(name) \ HandlerFrame Handler_##name(& (Handler) (name)); \ name #define handlerin(class,member,name) \ HandlerFrame Handler_##class##_##name(& (Handler) (class member name));\ class member name HandlerTest.C 591286103 137 20 100600 2870 ` #include "Handler.H" class Small; extern void abort(); extern int puts(const char *); extern int printf(const char * ...); class Small { private: signed char value; public: static Small (*onOverflow)(); /* constructor */ Small(); /* initializer */ Small(Small &); /* constructor */ Small(const int); /* converter */ operator int(); friend Small operator +(const Small &,const Small &); friend Small operator -(const Small &,const Small &); friend Small operator /(const Small &,const Small &); friend Small operator *(const Small &,const Small &); }; inline Small::Small ( ) { this->value = '\0'; } inline Small::Small ( Small &initial ) { value = initial.value; } inline Small::Small ( const int initial ) { value = initial; printf("Constructor initial %d, value %d\n",initial,this->value); if (this->value != initial) this->value = (*Small::onOverflow)(); } inline int Small::operator int ( ) { return value; } inline Small operator + ( register const Small &a, register const Small &b ) { register const Small c((int) a.value + (int) b.value); return c; } inline Small operator - ( register const Small &a, register const Small &b ) { register const Small c((int) a.value - (int) b.value); return c; } inline Small operator * ( register const Small &a, register const Small &b ) { register const Small c((int) a.value * (int) b.value); return c; } inline Small operator / ( register const Small &a, register const Small &b ) { return (b.value != '\0') ? (int) a.value / (int) b.value : (*Small::onOverflow)(); } static Small Terminate ( ) { puts("Terminating because of overflow!"); abort(); *(char *) 0; return '\0'; } static Small Oops ( ) { puts("You naughty boy! you overflowed, but you get a reprieve."); return 127; } int main ( int argc, char *(argv[]), char *(envp[]) ) { Small::onOverflow = &Terminate; printf("sizeof (Small) %u\n",(unsigned) sizeof (Small)); Small a(100); Small b(60); Small c; printf("After declarations a %d, b %d, c %d\n",(int) a,(int) b,(int) c); { handlerin(Small,::,onOverflow) = &Oops; printf("Small::onOverflow 0x%#lx, Terminate 0x%#lx, Oops 0x%#lx\n", (long) Small::onOverflow, (long) &Terminate, (long) &Oops); c = a + b; printf(" c %d = a %d + b %d\n",(int) c,(int) a,(int) b); } printf("Small::onOverflow 0x%#lx, Terminate 0x%#lx, Oops 0x%#lx\n", (long) Small::onOverflow, (long) &Terminate, (long) &Oops); c = a + b; printf(" c %d = a %d + b %d\n",(int) c,(int) a,(int) b); } Trace.3 592524030 137 20 100600 2473 ` .TH TRACE 3+ "PCG" .ad .SH NAME Trace, TraceOF(), TraceDO() \- trace functions on entry, body, exit. .SH SYNOPSIS .nf .B "#include " .sp .BI "Trace aname(" string ");" .IB "aname" "(" string ");" .sp .BI "TraceOF(" string ");" .BI "TraceDO(" expression ");" .fi .SH DESCRIPTION A new class is defined, .I Trace whose object may be used to trace function entry/exit. The constructor of a Trac eobject will expect a string, the contents of the object; an indentation level is incremented by the constructor, and that string (which ought to be the name of the function) will be printed, with an indication that that function has been entered. On exit from the function the destructor will be automatically called, which will print the string of the trace object and a note that the function has been exited. When a Trace object is applied to a string, the string in it will be printed (after suitable indentation), followed by the string being supplied as a parameter. .PP Of course you can have any block traced in this way; it will be however not very clever to declared more than object of class Trace per block... .PP The address of the .I stream object you want to use for tracing (usually .IR cerr ) will have to be put into .IR "Trace::traceStream" ; if this variable is zero then the trace will be suppressed. .PP An example of use: .PP .nf #include #include int main ( int argc, char **argv, char **envp ) { Trace::traceStream = &cerr; Trace trace("main"); trace("going to print a greeting..."); cout << "Hello world!\en"; trace(" greeting printed."); return 0; } .fi .PP Normally however you will want to use a couple of macros that expand the tracing statements only if the macro .I DEBUG has been defined. .PP the macro .I TraceOF will then expand to just a declation of a Trace object; the only parameter to TraceOF will be supplied to the Trace constructor. .I TraceDO is a macro with a single parameter; the parameter will be expanded verbatim only if DEBUG is defined. You will tipically use these macros like this: .PP .nf #define DEBUG // or usually with -DDEBUG to the compiler... #include #include int main ( int argc, char **argv, char **envp ) { Trace::traceStream = &cerr; TraceOF("main"); TraceDO(cerr << "going to print a greeting..."); cout << "Hello world!\en"; TraceDO(cerr << " greeting printed."); return 0; } .fi Trace.H 592523499 137 20 100600 1764 ` /* This class is intended to be used to trace procedures. The constructor will have the procedure name as argument, and it will print something to the effect that the procedure has been entered, increment the call level counter, and store the name of the procedure. The destructor will decrement the level counter and print that the procedure has been exited. In between the () operator can be called to give personalized tracing statements at the right level. Example: float Square(register const float x) { Trace trace("Square"); trace("x = %f\n", (float) x); float y = x * x; trace("x^2 = %f\n", (float) y); return y; } Normally we will want to conditionally expand these declarations and macro calls are thus provided for this purpose, to be used like this: float Square(register const float x) { TraceOF("Square"); TraceDO(("x = %f\n", (float) x)); float y = x * x; TraceDO(("x^2 = %f\n", (float) y)); return y; } */ #ifdef DEBUG # define TraceOF(name) Trace TraceThis(name) # define TraceDO(args) (TraceThis(), (args)) #else # define TraceOF(name) # define TraceDO(args) #endif class Trace { private: static short unsigned callLevel = 0; char *blockName; public: static void *traceStream = 0; Trace(const char *); void ~Trace(); int operator ()(const char * =": "); }; inline Trace::Trace ( const char *blockName ) { this->blockName = blockName; (*this)(" ENTERED\n"); Trace::callLevel++; } inline Trace::~Trace ( ) { --Trace::callLevel; (*this)(" EXITED\n"); } Trace.C 592523039 137 20 100600 793 ` #ifdef EXAMPLE # define DEBUG #endif #include #include "Trace.H" int Trace::operator () ( const char * const aLabel ) { if (traceStream == 0) return; { register unsigned indent; for (indent = callLevel; indent != 0; --indent) ((ostream *) traceStream)->put(' '); } (*(ostream *) traceStream) << this->blockName << aLabel; return callLevel; } #ifdef EXAMPLE float Square(register const float x) { TraceOF("Square"); TraceDO(cerr << "x = " << x << "\n"); float y = x * x; TraceDO(cerr << "x^2 = " << y << "\n"); return y; } int main(int argc, char **argv, char **envp) { Trace::traceStream = &cout; TraceOF("main"); { TraceOF("a block"); return int(Square(3.0)); } } #endif -- Piercarlo "Peter" Grandi INET: pcg@cs.aber.ac.uk Sw.Eng. Group, Dept. of Computer Science UUCP: ...!mcvax!ukc!aber-cs!pcg UCW, Penglais, Aberystwyth, WALES SY23 3BZ (UK)