├── doc ├── example4 ├── example3 ├── example8 ├── example2 ├── example5 ├── example11 ├── example7 ├── example9 ├── example10 ├── example12 ├── example1 ├── example6 ├── README ├── figure1 ├── sidebar1 ├── listing1 ├── listing2 └── text ├── Makefile ├── thread.c ├── Lifo.h ├── Hash.h ├── Alloc.h ├── Assert.c ├── List.h ├── Alloc.c ├── Assert.h ├── Lifo.c ├── Hash.c ├── Except.h ├── Test.c ├── List.c └── README /doc/example4: -------------------------------------------------------------------------------- 1 | Example 4: Superfluous catch. Catching a baseclass hides all subclass catches 2 | below it. 3 | 4 | 5 | try {} 6 | catch (RuntimeException) {} 7 | catch (OutOfMemoryError) {} 8 | catch (IllegalInstruction) {} /* never matched */ 9 | finally {} 10 | -------------------------------------------------------------------------------- /doc/example3: -------------------------------------------------------------------------------- 1 | Example 3: Duplicate catch. When an exception class appears in two catch 2 | clauses, only the first will ever catch matching exceptions. 3 | 4 | 5 | try {} 6 | catch (FailedAssertion) {} 7 | catch (RuntimeException) {} 8 | catch (FailedAssertion) {} /* never matched */ 9 | catch (Exception) {} 10 | finally {} 11 | -------------------------------------------------------------------------------- /doc/example8: -------------------------------------------------------------------------------- 1 | Example 8: More structural checks (e.g., dealing with wrong use of a library) 2 | are done with assert(), while behavioral checks (dealing with run-time 3 | conditions) are done with validate(). 4 | 5 | 6 | #include "Assert.h" 7 | 8 | void * ListRemoveHead(List *pList) 9 | { 10 | assert(pList != NULL); 11 | validate(pList->count > 0, NULL); 12 | ... 13 | } 14 | -------------------------------------------------------------------------------- /doc/example2: -------------------------------------------------------------------------------- 1 | Example 2: Using getClass() to determine the actual caught (base)class. Two 2 | (sub)classes are 'handled' and any other is propagated by a re-throw. 3 | 4 | 5 | catch (MyFault, e) { 6 | switch (e->getClass()) { 7 | case ReallyMyFault: 8 | case AlsoMyFault: 9 | /* I apologize */ 10 | break; 11 | default: 12 | throw (e, NULL); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /doc/example5: -------------------------------------------------------------------------------- 1 | Example 5: Throw works across function calls. Invoking what() prints 2 | "I'm fine, thank you!". 3 | 4 | 5 | there() 6 | { 7 | throw (Funny, "How are you?"); 8 | } 9 | 10 | hi() 11 | { 12 | there(); 13 | } 14 | 15 | what() 16 | { 17 | try { 18 | hi(); 19 | } 20 | catch (Funny, e) { 21 | printf("%s\n", e->getData()); 22 | } 23 | finally; 24 | } 25 | -------------------------------------------------------------------------------- /doc/example11: -------------------------------------------------------------------------------- 1 | Example 11: A VxWorks implementation of the application supplied routine for 2 | guarding changes to package internal data. This does disable task scheduling; 3 | so when deleting a task, there's no need to get hold of the lock. [VXW51] 4 | 5 | 6 | #include 7 | 8 | void LockUnlock(int mode) 9 | { 10 | if (mode == 0) 11 | taskUnlock(); 12 | else 13 | taskLock(); 14 | } 15 | -------------------------------------------------------------------------------- /doc/example7: -------------------------------------------------------------------------------- 1 | Example 7: Workaround when you want Java-like break behavior. The workaround 2 | for continue is similar. 3 | 4 | 5 | even(void) 6 | { 7 | int breaked = 0; 8 | 9 | while (...) 10 | { 11 | try 12 | if (needToBreak()) { 13 | breaked = 1; 14 | break; 15 | } 16 | catch (...) {} 17 | finally (...) {} 18 | 19 | if (breaked && breaked--) 20 | break; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /doc/example9: -------------------------------------------------------------------------------- 1 | Example 9: One of the memory allocation macros that throw OutOfMemoryError if 2 | the native call failed. 3 | 4 | 5 | a. Alloc.h: 6 | 7 | #define calloc(n, size) AllocCalloc(pC, n, size, __FILE__, __LINE__) 8 | 9 | 10 | b. Alloc.c (does not include Alloc.h): 11 | 12 | void * AllocCalloc(Context *pC, int number, int size, char *file, int line) 13 | { 14 | void *pMem; 15 | 16 | pMem = calloc(number, size); 17 | if (pMem == NULL) 18 | ExceptThrow(pC, OutOfMemoryError, NULL, file, line); 19 | 20 | return pMem; 21 | } 22 | -------------------------------------------------------------------------------- /doc/example10: -------------------------------------------------------------------------------- 1 | Example 10: This small program demonstrates signal handling and recursion. It 2 | prints: '0, 1, 2, 3, ArithmeticException: file "?", line 0.'. Note that it is 3 | impossible to supply file and line information for a signal exception. 4 | 5 | 6 | #include "Except.h" 7 | 8 | stacking(int x) 9 | { 10 | try { 11 | int y = 1 / x; 12 | stacking(x - 1); 13 | } 14 | finally 15 | printf("%d, ", x); 16 | } 17 | 18 | main() 19 | { 20 | try 21 | stacking(3); 22 | catch (RuntimeException, e) 23 | printf("%s\n", e->getMessage()); 24 | finally; 25 | } 26 | -------------------------------------------------------------------------------- /doc/example12: -------------------------------------------------------------------------------- 1 | Example 12: A Solaris threads implementation of the routine used by the package 2 | to guard critical changes to internal data. Because this routine does not 3 | disable thread scheduling, code killing a thread must hold the lock. [SOL26] 4 | 5 | 6 | #include 7 | 8 | void LockUnlock(int mode) 9 | { 10 | static mutex_t mutex; 11 | static int initialized; 12 | 13 | if (!initialized) { 14 | mutex_init(&mutex, 0, 0); 15 | initialized = 1; 16 | } 17 | 18 | if (mode == 0) 19 | mutex_unlock(&mutex); 20 | else 21 | mutex_lock(&mutex); 22 | } 23 | -------------------------------------------------------------------------------- /doc/example1: -------------------------------------------------------------------------------- 1 | Example 1: Really, this is C code! When the critical code throws a MyFault 2 | exception or any of its subclasses, it is handled. For any other exception a 3 | message containing its name and where thrown (file & line) are printed, 4 | followed by a rethrow. The clean up code is always executed. 5 | 6 | 7 | #include "Except.h" 8 | 9 | ex_class_define(MyFault, Exception); 10 | ex_class_define(AlsoMyFault, MyFault); 11 | ex_class_define(AgainMyFault, MyFault); 12 | 13 | void TryMe(void) 14 | { 15 | try { 16 | /* critical code */ 17 | } 18 | catch (MyFault, e) { 19 | /* handle one of my faults */ 20 | } 21 | catch (Throwable, e) { 22 | printf("%s\n", e->getMessage()); 23 | throw (e, NULL); 24 | } 25 | finally { 26 | /* clean up code */ 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /doc/example6: -------------------------------------------------------------------------------- 1 | Example 6: Propagation and overruling. This routine spells "M a g i c" and 2 | returns 2. The return(1) overrules the thrown Throwable and return(2) overrules 3 | return(1); subsequently return(2) is propagated to the outermost try statement 4 | in this routine, after which the native 'return 2' is finally executed. 5 | 6 | 7 | int spell(void) 8 | { 9 | try { 10 | try { 11 | try 12 | throw (Throwable, 0); 13 | catch (Throwable, e) 14 | return(1); 15 | finally { 16 | printf("M a "); 17 | return(2); 18 | } 19 | } 20 | catch (Throwable, e) 21 | printf("Magic"); 22 | finally 23 | printf("g "); 24 | } 25 | finally 26 | printf("i c"); 27 | return(3); 28 | } 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES = Except.c Lifo.c Assert.c Alloc.c Hash.c List.c 2 | OBJECTS = $(SOURCES:.c=.o) 3 | PROGRAM = t 4 | 5 | CPPFLAGS = -DEXCEPT_MT_SHARED -DEXCEPT_THREAD_POSIX -DDEBUG #-DEXCEPT_DEBUG 6 | WARNINGS = -Wno-incompatible-pointer-types -Wno-unused-value -Wno-return-type -Wno-unused-value -Wno-null-dereference 7 | CFLAGS = -g -lpthread $(WARNINGS) #-fvolatile 8 | 9 | EX = except 10 | 11 | .KEEP_STATE: 12 | 13 | CC = cc 14 | 15 | %.o: %.c 16 | $(COMPILE.c) -o $@ $< 17 | 18 | default: $(PROGRAM) th 19 | 20 | $(OBJECTS) Test.o: Makefile $(SOURCES:.c=.h) 21 | 22 | $(PROGRAM): $(OBJECTS) Test.o 23 | $(LINK.c) -o $(PROGRAM) $(OBJECTS) Test.o 24 | 25 | th: $(OBJECTS) thread.c 26 | $(CC) thread.c -o th $(CPPFLAGS) $(CFLAGS) $(OBJECTS) 27 | 28 | clean: 29 | $(RM) $(OBJECTS) *.o *% core *.class $(PROGRAM) th *~ *.uu *.jar *.tar article/*% 30 | 31 | release: clean 32 | cd ..; jar cvf $(EX).jar $(SOURCES:%.c=$(EX)/%.c) $(SOURCES:%.c=$(EX)/%.h) $(EX)/Test.c $(EX)/README $(EX)/thread.c $(EX)/Makefile 33 | mv ../$(EX).jar . 34 | uuencode < $(EX).jar $(EX).jar > $(EX).jar.uu 35 | rm $(EX).jar 36 | cd ..; tar cvf $(EX).tar $(SOURCES:%.c=$(EX)/%.c) $(SOURCES:%.c=$(EX)/%.h) $(EX)/Test.c $(EX)/README $(EX)/thread.c $(EX)/Makefile 37 | mv ../$(EX).tar . 38 | gzip $(EX).tar 39 | uuencode < $(EX).tar.gz $(EX).tgz > $(EX).tgz.uu 40 | rm $(EX).tar.gz 41 | 42 | -------------------------------------------------------------------------------- /thread.c: -------------------------------------------------------------------------------- 1 | /* 2 | * cc thread.c -lpthread Except.o Assert.o Lifo.o Hash.o List.o 3 | */ 4 | 5 | #include 6 | #include "Except.h" 7 | 8 | #define NUM_THREADS 10 9 | #define NUM_LAUNCHERS 10 10 | 11 | void *launch(void *); 12 | void *thread(void *); 13 | 14 | int main(void) 15 | { 16 | pthread_t launchers[NUM_LAUNCHERS]; 17 | 18 | int i; 19 | 20 | try 21 | { 22 | for (i = 0; i < NUM_LAUNCHERS; i++) 23 | { 24 | pthread_create(&launchers[i], NULL, launch, (void *)0); 25 | } 26 | 27 | for (i = 0; i < NUM_LAUNCHERS; i++) 28 | { 29 | pthread_join(launchers[i], NULL); 30 | } 31 | } 32 | catch(Throwable, e) 33 | { 34 | e->printTryTrace(0); 35 | } 36 | finally; 37 | } 38 | 39 | 40 | void *launch(void *arg) 41 | { 42 | pthread_t threads[NUM_THREADS]; 43 | int i; 44 | 45 | for (i = 0; i < NUM_THREADS; i++) 46 | { 47 | pthread_create(&threads[i], NULL, thread, (void *)0); 48 | } 49 | 50 | for (i = 0; i < NUM_THREADS; i++) 51 | { 52 | pthread_join(threads[i], NULL); 53 | } 54 | printf("launch: reporting that all %d threads have terminated\n", i); 55 | 56 | return 0; 57 | } 58 | 59 | void *thread(void *arg) 60 | { 61 | try 62 | { 63 | *((int *)0) = 0; /* most probably causes runtime exception */ 64 | } 65 | catch (RuntimeException, e) 66 | { 67 | e->printTryTrace(0); 68 | } 69 | finally; 70 | } 71 | -------------------------------------------------------------------------------- /doc/README: -------------------------------------------------------------------------------- 1 | Article for October 1999 issue of Dr. Dobb's Journal 2 | ---------------------------------------------------- 3 | 4 | Title: Let's Finally Try to Catch C 5 | Sub: Centralized exception handling in C 6 | By: Cornelis van der Bent 7 | 8 | 9 | This directory contains the following files: 10 | 11 | text - the plain ASCII (line width 80) article text including author 12 | biography and references. It's about 2500 (~2550 not counting 13 | author biography and references) words as Jon suggested. 14 | 15 | exampleNN - 12 code example files including headings in plain ASCII (80). 16 | 17 | sidebar1 - Side bar of 500 words. A bit long, but yet interesting; it does 18 | not contain useless details. 19 | 20 | figure1 - ASCII text figure with heading. 21 | 22 | figureN.fm - 4 diagrams in FrameMaker format. Each includes a heading. 23 | 24 | figureN.gif - Same 4 diagrams as GIF image. 25 | 26 | listing1,2 - Two listings with a total length of 454 lines. Longer than the 27 | Author Guidelines suggest, I know. I think that many readers 28 | will, for this article, be very interested in the code because 29 | it's not features or concepts alone that are interesting. Just 30 | as I would, readers will want to know/see how to create something 31 | so similar to C++ and Java (even with inheritance) in C! 32 | 33 | I already stripped away the hundreds of comment lines, removed 34 | vertical white space and left out not essential details. 35 | Minimizing the length much further will do no good. 36 | 37 | - - - - - - - - - - - - - 38 | -------------------------------------------------------------------------------- /doc/figure1: -------------------------------------------------------------------------------- 1 | Figure 1: Summary of the features. 2 | 3 | * Catching all four kinds of exceptions: explicitly thrown by application, 4 | out-of-memory errors, failed assertions, and synchronous signals (traps). 5 | * Unlimited user extendible exception class hierarchy. Classes can extend 6 | classes defined in other (compiled) modules. An exception class can be 7 | caught with any (in)direct ancestor or itself. Just like in Java and C++. 8 | * Can be used in any multi-threading environment. Compile-time configuration. 9 | Only requires two platform specific functions: one getting the thread ID and 10 | the other for mutual exclusion locking. 11 | * Unlimited nesting in try, catch and finally blocks. 12 | * Exception propagation, overruling and rethrowing identical to Java. Also 13 | works properly for recursive functions. 14 | * Run-time check for duplicate and superfluous catch clauses (DEBUG option). 15 | A similar check as Java and C++ do at compile-time. 16 | * Always executed finally block, also when using return() (macro), break or 17 | continue. 18 | * Looks very similar to Java (or C++). C implementation details are nicely 19 | hidden. Very little side effects and irregularities. 20 | * Caught exception member functions like getMessage(). No context pointer 21 | as first argument; gives a real OO look. 22 | * All messages (except those of signals) contain file name and line number. 23 | * Default behavior or a message printed, for not caught exceptions. 24 | * Fully ANSI C compliant code and no platform dependencies. 25 | * Well documented: source code, extensive README and this article. (Due to 26 | limited space the comments and horizontal space were stripped from the 27 | listings in this article.) 28 | * Free, source code available. 29 | -------------------------------------------------------------------------------- /Lifo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Lifo.h - LIFO buffer library header 3 | * 4 | * DESCRIPTION 5 | * This header belongs to "Lifo.c" and must be included by every module 6 | * that uses LIFO buffers. 7 | * 8 | * COPYRIGHT 9 | * You are free to use, copy or modify this software at your own risk. 10 | * 11 | * AUTHOR 12 | * Cornelis van der Bent. Please let me know if you have comments or find 13 | * flaws: cg_vanderbent@mail.com. Enjoy! 14 | * 15 | * MODIFICATION HISTORY 16 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 17 | * 1998/12/18 vdbent Conception. 18 | */ 19 | 20 | #ifndef _LIFO_H 21 | #define _LIFO_H 22 | 23 | typedef struct /* LIFO buffer */ 24 | { 25 | void ** pObjects; /* user object 'array' */ 26 | int size; /* size of object 'array' */ 27 | int pointer; /* stack pointer points to free 'array' item */ 28 | } Lifo; 29 | 30 | 31 | extern 32 | Lifo * LifoCreate(void); 33 | 34 | extern 35 | void LifoDestroy( 36 | Lifo * pLifo); /* pointer to LIFO buffer */ 37 | 38 | extern 39 | void LifoDestroyData( 40 | Lifo * pLifo); /* pointer to LIFO buffer */ 41 | 42 | extern 43 | void LifoPush( 44 | Lifo * pLifo, /* pointer to LIFO buffer */ 45 | void * pObject); /* object being added */ 46 | 47 | extern 48 | void * LifoPop( 49 | Lifo * pLifo); /* pointer to LIFO buffer */ 50 | 51 | extern 52 | void * LifoPeek( 53 | Lifo * pLifo, /* pointer to LIFO buffer */ 54 | int number); /* object number to get (0: top) */ 55 | 56 | extern 57 | int LifoCount( 58 | Lifo * pLifo); /* pointer to LIFO buffer */ 59 | 60 | 61 | #endif /* _LIFO_H */ 62 | -------------------------------------------------------------------------------- /Hash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Hash.h - hash table library header 3 | * 4 | * DESCRIPTION 5 | * This header belongs to "Hash.c" and must be included by every module 6 | * that uses hash tables. 7 | * 8 | * COPYRIGHT 9 | * You are free to use, copy or modify this software at your own risk. 10 | * 11 | * AUTHOR 12 | * Cornelis van der Bent. Please let me know if you have comments or find 13 | * flaws: cg_vanderbent@mail.com. Enjoy! 14 | * 15 | * MODIFICATION HISTORY 16 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 17 | * 1998/12/18 vdbent Conception. 18 | */ 19 | 20 | #ifndef _HASH_H 21 | #define _HASH_H 22 | 23 | #include "List.h" 24 | 25 | typedef struct _Hash Hash; /* hash table */ 26 | struct _Hash 27 | { 28 | List ** pNodeLists; /* node lists */ 29 | int count; /* number of stored nodes */ 30 | }; 31 | 32 | 33 | extern 34 | Hash * HashCreate(void); 35 | 36 | extern 37 | void HashDestroy( 38 | Hash * pHash); /* pointer to hash table */ 39 | 40 | extern 41 | void HashDestroyData( 42 | Hash * pHash); /* pointer to hash table */ 43 | 44 | extern 45 | void * HashLookup( 46 | Hash * pHash, /* pointer to hash table */ 47 | int key); /* key number */ 48 | 49 | extern 50 | void HashAdd( 51 | Hash * pHash, /* pointer to hash table */ 52 | int key, /* key number */ 53 | void * pData); /* user data to be stored */ 54 | 55 | extern 56 | void * HashRemove( 57 | Hash * pHash, /* pointer to hash table */ 58 | int key); /* key number */ 59 | 60 | extern 61 | int HashCount( 62 | Hash * pHash); /* pointer to hash table */ 63 | 64 | 65 | #endif /* _HASH_H */ 66 | -------------------------------------------------------------------------------- /Alloc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Alloc.h - memory allocation module header 3 | * 4 | * DESCRIPTION 5 | * This header contains macros that replace the standard C memory 6 | * allocation routines. They throw an exception when there is not 7 | * enough memory. The macros use functions defined in "Alloc.c". 8 | * The handy new() macro is added; it only needs the type name. 9 | * 10 | * Using macros allows the file name and line number information supplied 11 | * by the preprocessor, to be available for error reporting. 12 | * 13 | * COPYRIGHT 14 | * You are free to use, copy or modify this software at your own risk. 15 | * 16 | * AUTHOR 17 | * Cornelis van der Bent. Please let me know if you have comments or find 18 | * flaws: cg_vanderbent@mail.com. Enjoy! 19 | * 20 | * MODIFICATION HISTORY 21 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 22 | * 1998/12/18 vdbent Conception. 23 | */ 24 | 25 | #ifndef _ALLOC_H 26 | #define _ALLOC_H 27 | 28 | #include 29 | #include "Except.h" 30 | 31 | 32 | #define new(type) AllocCalloc(pC, 1, sizeof(type), __FILE__, __LINE__) 33 | 34 | #define calloc(n, size) AllocCalloc(pC, n, size, __FILE__, __LINE__) 35 | 36 | #define malloc(size) AllocMalloc(pC, size, __FILE__, __LINE__) 37 | 38 | #define realloc(p,size) AllocRealloc(pC, p, size, __FILE__, __LINE__) 39 | 40 | 41 | extern 42 | void * AllocCalloc( 43 | Context * pC, /* pointer to thread exception context */ 44 | int number, /* number of elements */ 45 | int size, /* size of one element */ 46 | char * file, /* name of source file where invoked */ 47 | int line); /* source file line number */ 48 | 49 | extern 50 | void * AllocMalloc( 51 | Context * pC, /* pointer to thread exception context */ 52 | int size, /* size of one element */ 53 | char * file, /* name of source file where invoked */ 54 | int line); /* source file line number */ 55 | 56 | extern 57 | void * AllocRealloc( 58 | Context * pC, /* pointer to thread exception context */ 59 | void * p, /* pointer to original block */ 60 | int size, /* size of one element */ 61 | char * file, /* name of source file where invoked */ 62 | int line); /* source file line number */ 63 | 64 | 65 | #endif /* _ALLOC_H */ 66 | -------------------------------------------------------------------------------- /Assert.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Assert.c - assertion check module 3 | * 4 | * DESCRIPTION 5 | * This module contains a routine that is used by the assert(), validate() 6 | * and check() macros which are defined in "Assert.h". This routine must 7 | * not be called by the user. 8 | * 9 | * Having a routine for processing failed assertions, instead of having 10 | * a macro only (like the standard C assert() macro), allows placing a 11 | * debugger breakpoint. 12 | * 13 | * INCLUDE FILES 14 | * Assert.h 15 | * 16 | * COPYRIGHT 17 | * You are free to use, copy or modify this software at your own risk. 18 | * 19 | * AUTHOR 20 | * Cornelis van der Bent. Please let me know if you have comments or find 21 | * flaws: cg_vanderbent@mail.com. Enjoy! 22 | * 23 | * MODIFICATION HISTORY 24 | * 1999/05/14 vdbent Changes for version 1.0. 25 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 26 | * 1998/12/18 vdbent Conception. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include "Except.h" 33 | 34 | 35 | /****************************************************************************** 36 | * 37 | * AssertAction - process failed assertion 38 | * 39 | * DESCRIPTION 40 | * This routine is invoked when an assertion has failed. It is used by 41 | * the macros assert(), validate() and check(), which are defined in 42 | * "Assert.h". 43 | * 44 | * When in exception handling context, it throws an EX_ASSERT exception; 45 | * besides the file name and the line number, the exception string is also 46 | * passed for the user to be retrieved inside a 'catch' clause using the 47 | * e->getData() member function. 48 | * 49 | * When not in exception handling context, the standard message will be 50 | * printed on . In this situation the flag is used to 51 | * determine if abort() is to be invoked (causing 'core dump') or not. 52 | * 53 | * SIDE EFFECTS 54 | * When is true, there is nothing more to say when not in 55 | * exception handling context. When in exception handling context an 56 | * exception is thrown. 57 | * 58 | * RETURNS 59 | * N/A. 60 | */ 61 | 62 | void AssertAction( 63 | Context * pC, /* pointer to thread exception context */ 64 | int doAbort, /* flag if abort() must be invoked */ 65 | char * expr, /* failed expression string */ 66 | char * file, /* name of source file where failed */ 67 | int line) /* source file line number */ 68 | { 69 | Scope scope = ExceptGetScope(pC); 70 | 71 | if (scope == TRY || scope == CATCH || scope == FINALLY) 72 | { 73 | if (pC == NULL) 74 | pC = ExceptGetContext(NULL); /* should never return NULL */ 75 | 76 | ExceptThrow(pC, FailedAssertion, expr, file, line); 77 | } 78 | else 79 | { 80 | fprintf(stderr, "Assertion failed %s: %s, file \"%s\", line %d.\n", 81 | doAbort ? "" : "(no abort)", expr, file, line); 82 | 83 | if (doAbort) 84 | abort(); 85 | } 86 | } 87 | 88 | 89 | /* end of Assert.c */ 90 | -------------------------------------------------------------------------------- /doc/sidebar1: -------------------------------------------------------------------------------- 1 | Sidebar 1: Considering why C++ does not support signals as exceptions. 2 | 3 | 4 | Bjarne Stroustrup argues about C++ exception handling that: 5 | 6 | "The mechanism is designed to handle only synchronous exceptions, such as 7 | array checks and I/O errors. Asynchronous events, such as keyboard 8 | interrupts and certain arithmetic errors, are not necessarily exceptional 9 | and are not handled directly by this mechanism. Asynchronous events 10 | require mechanisms fundamentally different from exceptions (as defined 11 | here) to handle them cleanly and efficiently. Many systems offer 12 | mechanisms, such as signals, to deal with asynchrony, but because these 13 | tend to be system-dependent, they are not described here." 14 | 15 | [STR97] - $14.1.1, page 357. 16 | 17 | According to the Solaris on-line manual: 18 | 19 | "Signals can be generated synchronously or asynchronously. Events directly 20 | caused by the execution of code by a thread, such as a reference to an 21 | unmapped, protected, or bad memory, ... are said to be synchronously 22 | generated." 23 | 24 | [SOL26] - signal(5). 25 | 26 | Indeed, as mister Stroustrup says, asynchronous events must not be handled by 27 | the same mechanism used for handling synchronous events. However, he does not 28 | mention synchronous signals (nor deals with signal handling) anywhere in his 29 | book [STR97]. Conceptually, synchronous signals and regular exceptions are the 30 | same. 31 | 32 | It is true that the facilities for signal handling offered by the various 33 | systems, differ a lot. However, all systems supporting standard C++ have the 34 | good old ANSI C definitions in common: 35 | 36 | "The header provides facilities for handling exceptional 37 | conditions that arise during execution, such as an interrupt signal 38 | from an external source or an error in execution." 39 | 40 | [KER88] - Appendix B9, page 255. 41 | 42 | So by definition, all systems support six signals. Two typically coming 'from 43 | an external source' (i.e., asynchronous): SIGINT and SIGTERM, and the others 44 | caused by 'an error in execution' (i.e., synchronous): SIGABRT, SIGFPE, SIGILL 45 | and SIGSEGV. Furthermore there are two common library functions: signal() and 46 | raise(). 47 | 48 | The following aspects must be considered when dealing with differences in 49 | signal handling: 50 | * Some systems mask the signal during execution of its handler. As far as I 51 | know, this mask is always reset when the handler finishes (i.e., returns or 52 | performs a longjmp()). 53 | * Some systems set the signal's disposition to SIG_DFL (the default) prior to 54 | executing its handler. An exception handling mechanism will have installed a 55 | handler and requires it to be restored again when finished. Not all systems 56 | do this (for every signal). 57 | * In a multi-threaded environment, the threads either share the signal 58 | handlers, or each have a private set. 59 | * Again as far as I know, a synchronous signal is always delivered to and 60 | handled by the thread that initiated it even if the handlers are shared. 61 | The differences, only in the second and third aspect, are not fundamental. 62 | Simply re-installing the signal handler during its execution using signal(), 63 | settles the first difference. And, as my implementation shows, it is quite 64 | possible to support both shared and private handler schemes providing 65 | identical functionality. 66 | -------------------------------------------------------------------------------- /List.h: -------------------------------------------------------------------------------- 1 | /* 2 | * List.h - doubly linked list library header 3 | * 4 | * DESCRIPTION 5 | * This header belongs to "List.c" and must be included by every module 6 | * that uses doubly linked lists. 7 | * 8 | * COPYRIGHT 9 | * You are free to use, copy or modify this software at your own risk. 10 | * 11 | * AUTHOR 12 | * Cornelis van der Bent. Please let me know if you have comments or find 13 | * flaws: cg_vanderbent@mail.com. Enjoy! 14 | * 15 | * MODIFICATION HISTORY 16 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 17 | * 1998/12/18 vdbent Conception. 18 | */ 19 | 20 | #ifndef _LIST_H 21 | #define _LIST_H 22 | 23 | typedef struct _ListNode ListNode; 24 | struct _ListNode 25 | { 26 | ListNode * pNext; /* next node ('down', 'after') */ 27 | ListNode * pPrev; /* next node ('up', 'before') */ 28 | void * pData; /* pointer to user data */ 29 | }; 30 | 31 | typedef struct _List List; /* doubly linked list */ 32 | struct _List 33 | { 34 | ListNode * pHead; /* pointer to dummy list head node */ 35 | ListNode * pNodeLast; /* pointer to last accessed node */ 36 | int count; /* number of user nodes in list */ 37 | }; 38 | 39 | 40 | extern 41 | List * ListCreate(void); 42 | 43 | extern 44 | void ListDestroy( 45 | List * pList); /* pointer to list */ 46 | 47 | extern 48 | void ListDestroyData( 49 | List * pList); /* pointer to list */ 50 | 51 | extern 52 | void ListAddHead( 53 | List * pList, /* pointer to list */ 54 | void * pData); /* data object value */ 55 | 56 | extern 57 | void ListAddTail( 58 | List * pList, /* pointer to list */ 59 | void * pData); /* data object value */ 60 | 61 | extern 62 | void ListAddBefore( 63 | List * pList, /* pointer to list */ 64 | void * pData); /* data object value */ 65 | 66 | extern 67 | void ListAddAfter( 68 | List * pList, /* pointer to list */ 69 | void * pData); /* data object value */ 70 | 71 | extern 72 | void * ListRemoveHead( 73 | List * pList); /* pointer to list */ 74 | 75 | extern 76 | void * ListRemoveTail( 77 | List * pList); /* pointer to list */ 78 | 79 | extern 80 | void * ListRemove( 81 | List * pList, /* pointer to list */ 82 | void * pData); /* data object value */ 83 | 84 | extern 85 | void * ListRemoveLast( 86 | List * pList); /* pointer to list */ 87 | 88 | extern 89 | void * ListHead( 90 | List * pList); /* pointer to list */ 91 | 92 | extern 93 | void * ListTail( 94 | List * pList); /* pointer to list */ 95 | 96 | extern 97 | void * ListLast( 98 | List * pList); /* pointer to list */ 99 | 100 | extern 101 | void * ListNext( 102 | List * pList); /* pointer to list */ 103 | 104 | extern 105 | void * ListPrev( 106 | List * pList); /* pointer to list */ 107 | 108 | extern 109 | int ListCount( 110 | List * pList); /* pointer to list */ 111 | 112 | extern 113 | void * ListFind( 114 | List * pList, /* pointer to list */ 115 | void * pData); /* data object value */ 116 | 117 | extern 118 | List * ListSplitBefore( 119 | List * pListOrg); /* pointer to original list */ 120 | 121 | extern 122 | List * ListSplitAfter( 123 | List * pListOrg); /* pointer to original list */ 124 | 125 | extern 126 | List * ListConcat( 127 | List * pListDst, /* pointer to destination list */ 128 | List * pListAdd); /* pointer to list to be added at tail */ 129 | 130 | 131 | #endif /* _LIST_H */ 132 | -------------------------------------------------------------------------------- /Alloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Alloc.c - memory allocation module 3 | * 4 | * DESCRIPTION 5 | * This module contains routines that are used by the macros defined in 6 | * "Alloc.h". None of these routines must be directly called by the user. 7 | * 8 | * INCLUDE FILES 9 | * Alloc.h 10 | * 11 | * COPYRIGHT 12 | * You are free to use, copy or modify this software at your own risk. 13 | * 14 | * AUTHOR 15 | * Cornelis van der Bent. Please let me know if you have comments or find 16 | * flaws: cg_vanderbent@mail.com. Enjoy! 17 | * 18 | * MODIFICATION HISTORY 19 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 20 | * 1998/12/18 vdbent Conception. 21 | */ 22 | 23 | #include 24 | #include "Except.h" 25 | 26 | 27 | /****************************************************************************** 28 | * 29 | * AllocCalloc - allocate a cleared chunk of memory 30 | * 31 | * DESCRIPTION 32 | * This routine invokes the calloc() C library function for allocating 33 | * a chunk of cleared memory. 34 | * 35 | * SIDE EFFECTS 36 | * An EX_MEMORY exception is thrown when there's not enough memory. 37 | * 38 | * RETURNS 39 | * Pointer to allocated memory. 40 | */ 41 | 42 | void * AllocCalloc( 43 | Context * pC, /* pointer to thread exception context */ 44 | int number, /* number of elements */ 45 | int size, /* size of one element */ 46 | char * file, /* name of source file where invoked */ 47 | int line) /* source file line number */ 48 | { 49 | void * pMem; 50 | 51 | pMem = calloc(number, size); 52 | if (pMem == NULL) 53 | ExceptThrow(pC, OutOfMemoryError, NULL, file, line); 54 | 55 | return pMem; 56 | } 57 | 58 | 59 | /****************************************************************************** 60 | * 61 | * AllocMalloc - allocate chunk of memory 62 | * 63 | * DESCRIPTION 64 | * This routine invokes the malloc() C library function for allocating 65 | * a chunk of memory. 66 | * 67 | * SIDE EFFECTS 68 | * An EX_MEMORY exception is thrown when there's not enough memory. 69 | * 70 | * RETURNS 71 | * Pointer to allocated memory. 72 | */ 73 | 74 | void * AllocMalloc( 75 | Context * pC, /* pointer to thread exception context */ 76 | int size, /* size of one element */ 77 | char * file, /* name of source file where invoked */ 78 | int line) /* source file line number */ 79 | { 80 | void * pMem; 81 | 82 | pMem = malloc(size); 83 | if (pMem == NULL) 84 | ExceptThrow(pC, OutOfMemoryError, NULL, file, line); 85 | 86 | return pMem; 87 | } 88 | 89 | 90 | /****************************************************************************** 91 | * 92 | * AllocRealloc - change size of allocated memory chunk 93 | * 94 | * DESCRIPTION 95 | * This routine invokes the realloc() C library function for changing 96 | * the size of

. 97 | * 98 | * SIDE EFFECTS 99 | * An EX_MEMORY exception is thrown when there's not enough memory. 100 | * 101 | * RETURNS 102 | * Pointer to allocated memory. 103 | */ 104 | 105 | void * AllocRealloc( 106 | Context * pC, /* pointer to thread exception context */ 107 | void * p, /* pointer to original block */ 108 | int size, /* size of one element */ 109 | char * file, /* name of source file where invoked */ 110 | int line) /* source file line number */ 111 | { 112 | void * pMem; 113 | 114 | pMem = realloc(p, size); 115 | if (pMem == NULL) 116 | ExceptThrow(pC, OutOfMemoryError, NULL, file, line); 117 | 118 | return pMem; 119 | } 120 | 121 | 122 | /* end of Alloc.c */ 123 | -------------------------------------------------------------------------------- /Assert.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Assert.h - assertion check module header 3 | * 4 | * DESCRIPTION 5 | * This header file defines the assert(), validate() and check() macros. 6 | * The assert() macro replaces the one defined in the standard C header 7 | * "assert.h". 8 | * 9 | * When DEBUG is defined, all three macros will invoke AssertAction() 10 | * which performs failed assertion processing. Having a routine instead 11 | * of only a macro (like the standard C assert() macro), allows placing a 12 | * debugger breakpoint. 13 | * 14 | * When DEBUG is not defined then assert() is an empty macro; validate() 15 | * and check() will still perform the test, but won't call AssertAction() 16 | * on failure. Instead, validate() will let the current routine return 17 | * its second parameter. When this routine returns void, NOTHING must be 18 | * used as second parameter (NOTHING is empty, and it will satisfy the 19 | * preprocessor). And on failure, the check() macro will throw an excep- 20 | * tion with its second parameter as exception number. 21 | * 22 | * The validate() and check() macros can be used in situations where a 23 | * failure is severe enough to cause a failed assertion during DEBUG, and 24 | * where a defined action (either returning or throwing) is required in 25 | * the production version (preventing (immediate) application failure). 26 | * 27 | * In contrary with standard C, this assert() will not invoke abort() by 28 | * default; so the application will not stop on the first failure, which 29 | * seems the preferred choice during DEBUG. Defining ASSERT_ABORT will 30 | * enable aborting. 31 | * 32 | * The shortest way of defining assert(e) is: 33 | * 34 | * if (!(e)) 35 | * AssertAction(pC, DO_ABORT, #e, __FILE__, __LINE__); 36 | * 37 | * but this would lead to erroneous code when used in a construct like: 38 | * 39 | * if (...) 40 | * assert(...); 41 | * else 42 | * { 43 | * } 44 | * 45 | * because the preprocessor output would be (indented as the compiler 46 | * sees it): 47 | * 48 | * if (...) 49 | * if (!(...)) 50 | * AssertAction(pC, 0, "...", "file,c", 123); 51 | * else 52 | * { 53 | * } 54 | * 55 | * The 'else' becomes part of the assertion check instead of the user 'if' 56 | * statement! To prevent situations like this: "dangling 'if'", the 57 | * 'if' statement in the macros below all have an 'else' clause. 58 | * 59 | * COPYRIGHT 60 | * You are free to use, copy or modify this software at your own risk. 61 | * 62 | * AUTHOR 63 | * Cornelis van der Bent. Please let me know if you have comments or find 64 | * flaws: cg_vanderbent@mail.com. Enjoy! 65 | * 66 | * MODIFICATION HISTORY 67 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 68 | * 1998/12/18 vdbent Conception. 69 | */ 70 | 71 | #ifndef _ASSERT_H 72 | #define _ASSERT_H 73 | 74 | #include "Except.h" 75 | 76 | #ifdef ASSERT_ABORT 77 | #define DO_ABORT 1 78 | #else 79 | #define DO_ABORT 0 80 | #endif 81 | 82 | #undef assert 83 | 84 | #ifdef DEBUG 85 | #define assert(e) \ 86 | if (e) \ 87 | { \ 88 | /* prevent dangling 'if' */ \ 89 | } \ 90 | else \ 91 | { \ 92 | AssertAction(pC, DO_ABORT, #e, __FILE__, __LINE__); \ 93 | } 94 | #else /* DEBUG */ 95 | #define assert(e) 96 | #endif /* DEBUG */ 97 | 98 | #define NOTHING /* 'void' validate return value */ 99 | 100 | #define validate(e, r) \ 101 | if (e) \ 102 | { \ 103 | /* prevent dangling 'if' */ \ 104 | } \ 105 | else \ 106 | { \ 107 | assert(e); \ 108 | return r; \ 109 | } 110 | 111 | #define check(e, n) \ 112 | if (e) \ 113 | { \ 114 | /* prevent dangling 'if' */ \ 115 | } \ 116 | else \ 117 | { \ 118 | assert(e); \ 119 | throw(n, NULL); \ 120 | } 121 | 122 | 123 | extern 124 | void AssertAction( 125 | Context * pC, /* pointer to thread exception context */ 126 | int doAbort, /* flag if abort() must be invoked */ 127 | char * expr, /* failed expression string */ 128 | char * file, /* name of source file where failed */ 129 | int line); /* source file line number */ 130 | 131 | 132 | #endif /* _ASSERT_H */ 133 | 134 | -------------------------------------------------------------------------------- /Lifo.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Lifo.c - LIFO buffer library 3 | * 4 | * DESCRIPTION 5 | * This module contains routines for managing LIFO buffers (i.e., stacks). 6 | * The size of a LIFO buffer is increased automatically when needed, so 7 | * it never becomes full. The size is however never decreased. 8 | * 9 | * INCLUDE FILES 10 | * Lifo.h 11 | * 12 | * COPYRIGHT 13 | * You are free to use, copy or modify this software at your own risk. 14 | * 15 | * AUTHOR 16 | * Cornelis van der Bent. Please let me know if you have comments or find 17 | * flaws: cg_vanderbent@mail.com. Enjoy! 18 | * 19 | * MODIFICATION HISTORY 20 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 21 | * 1998/12/18 vdbent Conception. 22 | */ 23 | 24 | #include 25 | #include "Lifo.h" 26 | #include "Assert.h" /* includes "Except.h" which defines return() macro */ 27 | 28 | #define INIT_SIZE 32 /* initial size */ 29 | #define INCR_SIZE 32 /* size increment when not enough space */ 30 | 31 | 32 | /****************************************************************************** 33 | * 34 | * LifoCreate - create LIFO buffer of unlimited size 35 | * 36 | * DESCRIPTION 37 | * This routine creates an empty LIFO buffer. The size is increased when 38 | * needed. 39 | * 40 | * SIDE EFFECTS 41 | * None. 42 | * 43 | * RETURNS 44 | * Pointer to LIFO buffer. 45 | */ 46 | 47 | Lifo * LifoCreate(void) 48 | { 49 | Lifo * pLifo; 50 | 51 | pLifo = malloc(sizeof(Lifo)); 52 | pLifo->pObjects = malloc(INIT_SIZE * sizeof(void *)); 53 | pLifo->size = INIT_SIZE; 54 | pLifo->pointer = 0; 55 | return pLifo; 56 | } 57 | 58 | 59 | /****************************************************************************** 60 | * 61 | * LifoDestroy - free LIFO buffer 62 | * 63 | * DESCRIPTION 64 | * This routine frees the memory that is occupied by the LIFO buffer. 65 | * 66 | * SIDE EFFECTS 67 | * None. 68 | * 69 | * RETURNS 70 | * N/A. 71 | */ 72 | 73 | void LifoDestroy( 74 | Lifo * pLifo) /* pointer to LIFO buffer */ 75 | { 76 | assert(pLifo != NULL); 77 | 78 | free(pLifo->pObjects); 79 | free(pLifo); 80 | } 81 | 82 | 83 | /****************************************************************************** 84 | * 85 | * LifoDestroyData - free LIFO buffer including user data 86 | * 87 | * DESCRIPTION 88 | * This routine frees the memory that is occupied by the LIFO buffer. The 89 | * user data objects are also freed using free(); the caller is responsi- 90 | * ble that all objects were allocated using routines compatible with 91 | * free(). 92 | * 93 | * SIDE EFFECTS 94 | * None. 95 | * 96 | * RETURNS 97 | * N/A. 98 | */ 99 | 100 | void LifoDestroyData( 101 | Lifo * pLifo) /* pointer to LIFO buffer */ 102 | { 103 | assert(pLifo != NULL); 104 | 105 | while(pLifo->pointer > 0) 106 | free(pLifo->pObjects[pLifo->pointer--]); 107 | 108 | free(pLifo->pObjects); 109 | free(pLifo); 110 | } 111 | 112 | 113 | /****************************************************************************** 114 | * 115 | * LifoPush - push object to LIFO buffer 116 | * 117 | * DESCRIPTION 118 | * This routine adds an object to the top of specified LIFO buffer. 119 | * 120 | * SIDE EFFECTS 121 | * None. 122 | * 123 | * RETURNS 124 | * N/A. 125 | */ 126 | 127 | void LifoPush( 128 | Lifo * pLifo, /* pointer to LIFO buffer */ 129 | void * pObject) /* object being added */ 130 | { 131 | assert(pLifo != NULL && pObject != NULL); 132 | 133 | if (pLifo->pointer == pLifo->size) { 134 | pLifo->size += INCR_SIZE; 135 | pLifo->pObjects = realloc(pLifo->pObjects, 136 | pLifo->size * sizeof(void *)); 137 | } 138 | pLifo->pObjects[pLifo->pointer++] = pObject; 139 | } 140 | 141 | 142 | /****************************************************************************** 143 | * 144 | * LifoPop - pop object from LIFO buffer 145 | * 146 | * DESCRIPTION 147 | * This routine removes an object from the top of the specified LIFO 148 | * buffer. 149 | * 150 | * It is illegal to perform this operation on an empty LIFO buffer (will 151 | * result in failed assertion when DEBUG defined, otherwise returns NULL). 152 | * 153 | * SIDE EFFECTS 154 | * None. 155 | * 156 | * RETURNS 157 | * Removed object, or NULL if LIFO buffer is empty. 158 | */ 159 | 160 | void * LifoPop( 161 | Lifo * pLifo) /* pointer to LIFO buffer */ 162 | { 163 | assert(pLifo != NULL); 164 | validate(pLifo->pointer > 0, NULL); 165 | 166 | return pLifo->pObjects[--pLifo->pointer]; 167 | } 168 | 169 | 170 | /****************************************************************************** 171 | * 172 | * LifoPeek - get specified LIFO buffer object 173 | * 174 | * DESCRIPTION 175 | * This routine returns the N-th object from the specified LIFO buffer 176 | * counting from the top (i.e., the first/top object is retrieved with 177 | * number equal to 1). The object is not removed. 178 | * 179 | * It is illegal to perform this operation on an empty LIFO buffer (will 180 | * result in failed assertion when DEBUG defined, otherwise returns NULL). 181 | * 182 | * SIDE EFFECTS 183 | * None. 184 | * 185 | * RETURNS 186 | * Specified object, or NULL if LIFO buffer is empty. 187 | */ 188 | 189 | void * LifoPeek( 190 | Lifo * pLifo, /* pointer to LIFO buffer */ 191 | int number) /* object number to get (1: top) */ 192 | { 193 | assert(pLifo != NULL); 194 | validate(pLifo->pointer > 0, NULL); 195 | validate(number > 0 && number <= pLifo->pointer, NULL); 196 | 197 | return pLifo->pObjects[pLifo->pointer - number]; 198 | } 199 | 200 | 201 | /****************************************************************************** 202 | * 203 | * LifoCount - get number of objects in LIFO buffer 204 | * 205 | * DESCRIPTION 206 | * This routine returns the number of objects in the specified LIFO 207 | * buffer. 208 | * 209 | * SIDE EFFECTS 210 | * None. 211 | * 212 | * RETURNS 213 | * Number of objects in LIFO buffer. 214 | */ 215 | 216 | int LifoCount( 217 | Lifo * pLifo) /* pointer to LIFO buffer */ 218 | { 219 | assert(pLifo != NULL); 220 | 221 | return pLifo->pointer; 222 | } 223 | 224 | 225 | /* end of Lifo.c */ 226 | -------------------------------------------------------------------------------- /doc/listing1: -------------------------------------------------------------------------------- 1 | typedef struct _Class Class[1]; /* exception class */ 2 | typedef struct _Class *ClassRef; /* exception class reference */ 3 | struct _Class { 4 | int notRethrown; /* always 1 (used by throw()) */ 5 | ClassRef parent; /* parent class */ 6 | char * name; /* this class name string */ 7 | int signalNumber; /* optional signal number */ 8 | }; 9 | 10 | typedef enum _Scope { /* exception handling scope */ 11 | OUTSIDE = -1, /* outside any 'try' */ 12 | INTERNAL, /* exception handling internal */ 13 | TRY, /* in 'try' (across routine calls) */ 14 | CATCH, /* in 'catch' (idem.) */ 15 | FINALLY /* in 'finally' (idem.) */ 16 | } Scope; 17 | 18 | typedef enum _State { /* exception handling state */ 19 | EMPTY, /* no exception occurred */ 20 | PENDING, /* exception occurred but not caught */ 21 | CAUGHT /* occurred exception caught */ 22 | } State; 23 | 24 | typedef struct _Except { /* exception handle */ 25 | int notRethrown; /* always 0 (used by throw()) */ 26 | State state; /* current state of this handle */ 27 | jmp_buf throwBuf; /* start-'catching' destination */ 28 | jmp_buf finalBuf; /* perform-'finally' destination */ 29 | ClassRef class; /* occurred exception class */ 30 | void * pData; /* exception associated (user) data */ 31 | char * file; /* exception file name */ 32 | int line; /* exception line number */ 33 | int ready; /* macro code control flow flag */ 34 | Scope scope; /* exception handling scope */ 35 | int first; /* flag if first try in function */ 36 | List * checkList; /* list used by 'catch' checking */ 37 | ClassRef (*getClass)(void); /* method returning class reference */ 38 | char * (*getMessage)(void); /* method getting description */ 39 | void * (*getData)(void); /* method getting application data */ 40 | } Except; 41 | 42 | typedef struct _Context { /* exception context per thread */ 43 | Except * pEx; /* current exception handle */ 44 | Lifo * exStack; /* exception handle stack */ 45 | char message[1024]; /* used by ExceptGetMessage() */ 46 | Handler sigAbrtHandler; /* default SIGABRT handler */ 47 | Handler sigFpeHandler; /* default SIGFPE handler */ 48 | Handler sigIllHandler; /* default SIGILL handler */ 49 | Handler sigSegvHandler; /* default SIGSEGV handler */ 50 | } Context; 51 | 52 | extern Context * pC; 53 | extern Class Throwable; 54 | 55 | #define ex_class_declare(child, parent) extern Class child 56 | #define ex_class_define(child, parent) Class child = { 1, parent, #child } 57 | #define ex_thread_cleanup(id) ExceptThreadCleanup(id) 58 | #define outermost ExceptThreadCleanup(-1); 59 | 60 | ex_class_declare(Exception, Throwable); 61 | ex_class_declare(OutOfMemoryError, Exception); 62 | ex_class_declare(FailedAssertion, Exception); 63 | ex_class_declare(RuntimeException, Exception); 64 | ex_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */ 65 | ex_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */ 66 | ex_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */ 67 | ex_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */ 68 | 69 | #ifdef DEBUG 70 | #define CHECKED \ 71 | static int checked 72 | #define CHECK_BEGIN(pC, pChecked, file, line) \ 73 | ExceptCheckBegin(pC, pChecked, file, line) 74 | #define CHECK(pC, pChecked, class, file, line) \ 75 | ExceptCheck(pC, pChecked, class, file, line) 76 | #define CHECK_END \ 77 | !checked 78 | #else /* DEBUG */ 79 | #define CHECKED 80 | #define CHECK_BEGIN(pC, pChecked, file, line) 1 81 | #define CHECK(pC, pChecked, class, file, line) 1 82 | #define CHECK_END 0 83 | #endif /* DEBUG */ 84 | 85 | #define try \ 86 | ExceptTry(pC); \ 87 | while (1) \ 88 | { \ 89 | Context * pTmpC = ExceptGetContext(pC); \ 90 | Context * pC = pTmpC; \ 91 | CHECKED; \ 92 | \ 93 | if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) && \ 94 | pC->pEx->ready && setjmp(pC->pEx->throwBuf) == 0) \ 95 | { \ 96 | pC->pEx->scope = TRY; \ 97 | do \ 98 | { 99 | 100 | #define catch(class, e) \ 101 | } \ 102 | while (0); \ 103 | } \ 104 | else if (CHECK(pC, &checked, class, __FILE__, __LINE__) && \ 105 | pC->pEx->ready && ExceptCatch(pC, class)) \ 106 | { \ 107 | Except *e = LifoPeek(pC->exStack, 1); \ 108 | pC->pEx->scope = CATCH; \ 109 | do \ 110 | { 111 | 112 | #define finally \ 113 | } \ 114 | while (0); \ 115 | } \ 116 | if (CHECK_END) \ 117 | continue; \ 118 | if (!pC->pEx->ready && setjmp(pC->pEx->finalBuf) == 0) \ 119 | pC->pEx->ready = 1; \ 120 | else \ 121 | break; \ 122 | } \ 123 | ExceptGetContext(pC)->pEx->scope = FINALLY; \ 124 | while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC)) \ 125 | while (ExceptGetContext(pC)->pEx->ready-- > 0) 126 | 127 | #define throw(pExceptOrClass, pData) \ 128 | ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__) 129 | 130 | #define return(x) \ 131 | { if (ExceptGetScope(pC) != OUTSIDE) { \ 132 | void *pData = malloc(sizeof(jmp_buf)); /* returnBuf */ \ 133 | ExceptGetContext(pC)->pEx->pData = pData; \ 134 | if (setjmp(*(jmp_buf *)pData) == 0) \ 135 | ExceptReturn(pC); \ 136 | else \ 137 | free(pData); \ 138 | } \ 139 | return x; \ 140 | } 141 | -------------------------------------------------------------------------------- /Hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Hash.c - hash table library for integer keys 3 | * 4 | * DESCRIPTION 5 | * This module contains routines for managing hash tables that use 6 | * integer numbers as key. 7 | * 8 | * INCLUDE FILES 9 | * Hash.h 10 | * 11 | * COPYRIGHT 12 | * You are free to use, copy or modify this software at your own risk. 13 | * 14 | * AUTHOR 15 | * Cornelis van der Bent. Please let me know if you have comments or find 16 | * flaws: cg_vanderbent@mail.com. Enjoy! 17 | * 18 | * MODIFICATION HISTORY 19 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 20 | * 1998/12/18 vdbent Conception. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "List.h" 26 | #include "Hash.h" 27 | #include "Assert.h" /* includes "Except.h" which defines return() macro */ 28 | 29 | #define HASH_SIZE 256 /* hash table size (power of 2 << HASH_SIZE) */ 30 | 31 | typedef struct /* hash table node */ 32 | { 33 | void * pData; /* pointer to the user data */ 34 | int key; /* key number */ 35 | } HashNode; 36 | 37 | 38 | /****************************************************************************** 39 | * 40 | * HashCreate - create hash table 41 | * 42 | * DESCRIPTION 43 | * This routine creates an empty hash table. 44 | * 45 | * SIDE EFFECTS 46 | * None. 47 | * 48 | * RETURNS 49 | * Pointer to hash table. 50 | */ 51 | 52 | Hash * HashCreate(void) 53 | { 54 | Hash * pHash; 55 | int n; 56 | 57 | pHash = malloc(sizeof(Hash)); 58 | 59 | pHash->count = 0; 60 | 61 | pHash->pNodeLists = malloc(HASH_SIZE * sizeof(List *)); 62 | for (n = 0; n < HASH_SIZE; n++) 63 | pHash->pNodeLists[n] = ListCreate(); 64 | 65 | return pHash; 66 | } 67 | 68 | 69 | /****************************************************************************** 70 | * 71 | * HashDestroy - free hash table 72 | * 73 | * DESCRIPTION 74 | * This routine frees the memory that is occupied by the hash table. 75 | * 76 | * SIDE EFFECTS 77 | * None. 78 | * 79 | * RETURNS 80 | * N/A. 81 | */ 82 | 83 | void HashDestroy( 84 | Hash * pHash) /* pointer to hash table */ 85 | { 86 | int n; 87 | 88 | assert(pHash != NULL); 89 | 90 | for (n = 0; n < HASH_SIZE; n++) 91 | ListDestroyData(pHash->pNodeLists[n]); 92 | 93 | free(pHash->pNodeLists); 94 | free(pHash); 95 | } 96 | 97 | 98 | /****************************************************************************** 99 | * 100 | * HashDestroyData - free hash table including user data 101 | * 102 | * DESCRIPTION 103 | * This routine frees the memory that is occupied by the hash table. The 104 | * user data in each entry is also freed using free(); the caller is 105 | * responsible that all of this user data was allocated using routines 106 | * compatible with free(). 107 | * 108 | * SIDE EFFECTS 109 | * None. 110 | * 111 | * RETURNS 112 | * N/A. 113 | */ 114 | 115 | void HashDestroyData( 116 | Hash * pHash) /* pointer to hash table */ 117 | { 118 | int n; 119 | 120 | assert(pHash != NULL); 121 | 122 | for (n = 0; n < HASH_SIZE; n++) 123 | { 124 | HashNode * pNode; 125 | 126 | pNode = ListHead(pHash->pNodeLists[n]); 127 | while (pNode != NULL) 128 | { 129 | free(pNode->pData); 130 | 131 | pNode = ListNext(pHash->pNodeLists[n]); 132 | } 133 | 134 | ListDestroyData(pHash->pNodeLists[n]); 135 | } 136 | 137 | free(pHash->pNodeLists); 138 | free(pHash); 139 | } 140 | 141 | 142 | /****************************************************************************** 143 | * 144 | * HashValue - calculate hash value 145 | * 146 | * DESCRIPTION 147 | * This routine calculates the hash value of integral key for 148 | * the specified hash table. 149 | * 150 | * INTERNAL 151 | * We use the 'multiplication method' as described in "Introduction to 152 | * Algorithms" by Thomas H. Cormen, Charles E. Leiserson, and Ronald L. 153 | * Rivest, MIT Press - 7th printing 1996, page 228. 154 | * 155 | * To avoid floating point operations and gain speed, the calculations are 156 | * done with integers by scaling with a factor 2^16. For 'A' we used the 157 | * golden ration as Knuth suggested. The modulo operation is implemented 158 | * with bitwise AND, and the division with right shift. 159 | * 160 | * Tests showed that the used constants indeed give a nice even distri- 161 | * bution of the hash values (no matter if is incremented with 1, 2, 162 | * 4, 8 or other values). 163 | * 164 | * The HASH_SIZE must be much lower than the scale factor. 165 | * 166 | * SIDE EFFECTS 167 | * None. 168 | * 169 | * RETURNS 170 | * Hash table index value. 171 | */ 172 | 173 | static int HashValue( 174 | Hash * pHash, /* pointer to hash table */ 175 | unsigned key) /* key number */ 176 | { 177 | int value; 178 | 179 | assert(pHash != NULL); 180 | 181 | value = (HASH_SIZE * ((key * 40503) & 65535)) >> 16; 182 | 183 | if (value >= HASH_SIZE || value < 0) 184 | fprintf(stderr, "INVALID HashValue: %d\n", value); 185 | 186 | return value; 187 | } 188 | 189 | 190 | /****************************************************************************** 191 | * 192 | * HashLookup - find hash node of key 193 | * 194 | * DESCRIPTION 195 | * This routine looks up the hash node value belonging to in the 196 | * specified hash table. If two or more nodes with the same key exist, 197 | * the most recent added to the hash table is returned. 198 | * 199 | * SIDE EFFECTS 200 | * None. 201 | * 202 | * RETURNS 203 | * Found hash node value, or NULL if not found. 204 | */ 205 | 206 | void * HashLookup( 207 | Hash * pHash, /* pointer to hash table */ 208 | int key) /* key number */ 209 | { 210 | List * nodeList; 211 | HashNode * pNode; 212 | 213 | assert(pHash != NULL); 214 | 215 | nodeList = pHash->pNodeLists[HashValue(pHash, key)]; 216 | 217 | pNode = ListHead(nodeList); 218 | while (pNode != NULL) 219 | { 220 | if (pNode->key == key) 221 | { 222 | return pNode->pData; 223 | } 224 | 225 | pNode = ListNext(nodeList); 226 | } 227 | 228 | return NULL; 229 | } 230 | 231 | 232 | /****************************************************************************** 233 | * 234 | * HashAdd - add node to hash table 235 | * 236 | * DESCRIPTION 237 | * This routine adds a node to the specified hash table. The node has 238 | * key and will store . 239 | * 240 | * The value may not be zero (because HashLookup() uses it as 241 | * 'not found' return value). 242 | * 243 | * SIDE EFFECTS 244 | * None. 245 | * 246 | * RETURNS 247 | * N/A. 248 | */ 249 | 250 | void HashAdd( 251 | Hash * pHash, /* pointer to hash table */ 252 | int key, /* key number */ 253 | void * pData) /* user data to be stored */ 254 | { 255 | HashNode * pNode; 256 | 257 | assert(pHash != NULL); 258 | validate(pData != 0, NOTHING); 259 | 260 | pNode = malloc(sizeof(HashNode)); 261 | pNode->key = key; 262 | pNode->pData = pData; 263 | 264 | ListAddHead(pHash->pNodeLists[HashValue(pHash, key)], pNode); 265 | pHash->count++; 266 | } 267 | 268 | 269 | /****************************************************************************** 270 | * 271 | * HashRemove - remove node from hash table 272 | * 273 | * DESCRIPTION 274 | * This routine removes the node defined by from the specified 275 | * hash table. 276 | * 277 | * Identical keys stored in the hash table are removed in FIFO order; this 278 | * means that the most recent added one is removed. 279 | * 280 | * SIDE EFFECTS 281 | * None. 282 | * 283 | * RETURNS 284 | * Removed node value, or NULL if not found. 285 | */ 286 | 287 | void * HashRemove( 288 | Hash * pHash, /* pointer to hash table */ 289 | int key) /* key number */ 290 | { 291 | List * nodeList; 292 | HashNode * pNode; 293 | void * pData; 294 | 295 | assert(pHash != NULL); 296 | 297 | nodeList = pHash->pNodeLists[HashValue(pHash, key)]; 298 | 299 | pNode = ListHead(nodeList); 300 | while (pNode != NULL) 301 | { 302 | if (pNode->key == key) 303 | { 304 | ListRemoveLast(nodeList); 305 | pData = pNode->pData; 306 | free(pNode); 307 | pHash->count--; 308 | 309 | return pData; 310 | } 311 | 312 | pNode = ListNext(nodeList); 313 | } 314 | 315 | return NULL; 316 | } 317 | 318 | 319 | /****************************************************************************** 320 | * 321 | * HashCount - get number of nodes in hash table 322 | * 323 | * DESCRIPTION 324 | * This routine returns the number of nodes in the specified hash table. 325 | * 326 | * SIDE EFFECTS 327 | * None. 328 | * 329 | * RETURNS 330 | * Number of nodes in hash table. 331 | */ 332 | 333 | int HashCount( 334 | Hash * pHash) /* pointer to hash table */ 335 | { 336 | assert(pHash != NULL); 337 | 338 | return pHash->count; 339 | } 340 | 341 | 342 | /* end of Hash.c */ 343 | -------------------------------------------------------------------------------- /doc/listing2: -------------------------------------------------------------------------------- 1 | Context * pC = NULL; 2 | Class Throwable = { 1, NULL, "Throwable" }; 3 | static Class ReturnEvent = { 1, NULL, "ReturnEvent" }; 4 | 5 | ex_class_define(Exception, Throwable); 6 | ex_class_define(OutOfMemoryError, Exception); 7 | ex_class_define(FailedAssertion, Exception); 8 | ex_class_define(RuntimeException, Exception); 9 | ex_class_define(AbnormalTermination, RuntimeException); /* SIGABRT */ 10 | ex_class_define(ArithmeticException, RuntimeException); /* SIGFPE */ 11 | ex_class_define(IllegalInstruction, RuntimeException); /* SIGILL */ 12 | ex_class_define(SegmentationFault, RuntimeException); /* SIGSEGV */ 13 | 14 | /******** get exception block scope ********/ 15 | Scope ExceptGetScope(Context *pC) { 16 | if (pC == NULL) pC = ExceptGetContext(NULL); 17 | if (pC == NULL || pC->pEx == NULL) 18 | return OUTSIDE; 19 | else 20 | return 0,((Except *)LifoPeek(pC->exStack, 1))->scope; 21 | } 22 | 23 | /******** get exception handling context of current thread ********/ 24 | Context * ExceptGetContext(Context *pC) { 25 | #if MULTI_THREADING 26 | if (pC == NULL && pContextHash != NULL) 27 | pC = HashLookup(pContextHash, EXCEPT_THREAD_ID_FUNC()); 28 | return pC; 29 | #else 30 | return &defaultContext; 31 | #endif 32 | } 33 | 34 | /******** create exception handling context for thread ********/ 35 | #if MULTI_THREADING 36 | static Context * ExceptCreateContext(void) { 37 | Context * pC 38 | 39 | EXCEPT_THREAD_LOCK_FUNC(1); 40 | pC = calloc(1, sizeof(Context)); 41 | HashAdd(pContextHash, EXCEPT_THREAD_ID_FUNC(), pC); 42 | EXCEPT_THREAD_LOCK_FUNC(0); 43 | return(pC); 44 | } 45 | #else 46 | #define ExceptCreateContext() NULL 47 | #endif 48 | 49 | /******** get current exception description string ********/ 50 | static char * ExceptGetMessage(void) { 51 | Context * pC = ExceptGetContext(NULL); 52 | 53 | sprintf(pC->message, "%s: file \"%s\", line %d.", 54 | pC->pEx->class->name, pC->pEx->file, pC->pEx->line); 55 | return pC->message; 56 | } 57 | 58 | /******** restore default signal handlers if needed ********/ 59 | static int ExceptRestoreHandlers(Context *pC) { 60 | int restored = 0; 61 | 62 | EXCEPT_THREAD_LOCK_FUNC(1); 63 | if (MULTI_THREADING && SHARE_HANDLERS && --numThreadsTry == 0) { 64 | signal(SIGABRT, sharedSigAbrtHandler); 65 | signal(SIGFPE, sharedSigFpeHandler); 66 | signal(SIGILL, sharedSigIllHandler); 67 | signal(SIGSEGV, sharedSigSegvHandler); 68 | restored = 1; 69 | } 70 | else if (!MULTI_THREADING || !SHARE_HANDLERS) { 71 | signal(SIGABRT, pC->sigAbrtHandler); 72 | signal(SIGFPE, pC->sigFpeHandler); 73 | signal(SIGILL, pC->sigIllHandler); 74 | signal(SIGSEGV, pC->sigSegvHandler); 75 | restored = 1; 76 | } 77 | EXCEPT_THREAD_LOCK_FUNC(0); 78 | return restored; 79 | } 80 | 81 | /******** cleanup exception handling for killed thread ********/ 82 | void ExceptThreadCleanup(int threadId) { 83 | #if MULTI_THREADING 84 | validate(threadId != EXCEPT_THREAD_ID_FUNC(), NOTHING); 85 | if (threadId == -1) threadId = EXCEPT_THREAD_ID_FUNC(); 86 | 87 | if (pContextHash != NULL) { 88 | Context * pC = HashLookup(pContextHash, threadId); 89 | if (pC != NULL) { 90 | EXCEPT_THREAD_LOCK_FUNC(1); 91 | ExceptRestoreHandlers(pC); 92 | LifoDestroyData(pC->exStack); 93 | if (pC->pEx->checkList != NULL) 94 | ListDestroyData(pC->pEx->checkList); 95 | free(HashRemove(pContextHash, threadId)); 96 | EXCEPT_THREAD_LOCK_FUNC(0); 97 | } 98 | } 99 | #endif 100 | } 101 | 102 | /******** 'throw' exception caused by signal ********/ 103 | static void ExceptThrowSignal(int number) { 104 | ClassRef class; 105 | 106 | switch (number) { 107 | case SIGABRT: class = AbnormalTermination; break; 108 | case SIGFPE: class = ArithmeticException; break; 109 | case SIGILL: class = IllegalInstruction; break; 110 | case SIGSEGV: class = SegmentationFault; break; 111 | } 112 | class->signalNumber = number; /* redundant after first time */ 113 | signal(number, ExceptThrowSignal); /* reinstall handler */ 114 | ExceptThrow(NULL, class, NULL, "?", 0); 115 | } 116 | 117 | /******** prepare for 'try' ********/ 118 | void ExceptTry(Context *pC) { 119 | int first; 120 | if (MULTI_THREADING && pContextHash == NULL) pContextHash = HashCreate(); 121 | if (first = (pC == NULL)) pC = ExceptGetContext(NULL); 122 | if (pC == NULL) pC = ExceptCreateContext(); 123 | 124 | EXCEPT_THREAD_LOCK_FUNC(1); 125 | if (pC->exStack == NULL) { 126 | pC->exStack = LifoCreate(); 127 | 128 | if (MULTI_THREADING && SHARE_HANDLERS && numThreadsTry++ == 0) { 129 | sharedSigAbrtHandler = signal(SIGABRT, ExceptThrowSignal); 130 | sharedSigFpeHandler = signal(SIGFPE, ExceptThrowSignal); 131 | sharedSigIllHandler = signal(SIGILL, ExceptThrowSignal); 132 | sharedSigSegvHandler = signal(SIGSEGV, ExceptThrowSignal); 133 | } 134 | else if (!MULTI_THREADING || !SHARE_HANDLERS) { 135 | pC->sigAbrtHandler = signal(SIGABRT, ExceptThrowSignal); 136 | pC->sigFpeHandler = signal(SIGFPE, ExceptThrowSignal); 137 | pC->sigIllHandler = signal(SIGILL, ExceptThrowSignal); 138 | pC->sigSegvHandler = signal(SIGSEGV, ExceptThrowSignal); 139 | } 140 | } 141 | LifoPush(pC->exStack, pC->pEx = calloc(sizeof(Except), 1)); 142 | pC->pEx->first = first; 143 | EXCEPT_THREAD_LOCK_FUNC(0); 144 | } 145 | 146 | /******** dispatch exception 'throw' ********/ 147 | void ExceptThrow(Context *pC, void *pEC, void *pData, char *file, int line) { 148 | if (pC == NULL) pC = ExceptGetContext(NULL); 149 | 150 | if (pC == NULL || pC->exStack == NULL || LifoCount(pC->exStack) == 0) { 151 | fprintf(stderr, "%s lost: file \"%s\", line %d.\n", 152 | ((ClassRef)pEC)->name, file, line); 153 | return; 154 | } 155 | if (((ClassRef)pEC)->notRethrown) { 156 | pC->pEx->class = (ClassRef)pEC; 157 | pC->pEx->pData = pData; 158 | pC->pEx->file = file; 159 | pC->pEx->line = line; 160 | pC->pEx->getMessage = ExceptGetMessage; 161 | pC->pEx->getData = ExceptGetData; 162 | } 163 | pC->pEx->state = PENDING; /* in case of throw() inside 'catch' */ 164 | switch (pC->pEx->scope) { 165 | case TRY: longjmp(pC->pEx->throwBuf, 1); 166 | case CATCH: longjmp(pC->pEx->finalBuf, 1); 167 | case FINALLY: longjmp(pC->pEx->finalBuf, 1); 168 | } 169 | } 170 | 171 | /******** determine if class is derived or identical ********/ 172 | static int ExceptIsDerived(ClassRef class, ClassRef base) { 173 | while (class->parent != NULL && class != base) 174 | class = class->parent; 175 | return class == base; 176 | } 177 | 178 | /******** check if exception can be caught ********/ 179 | int ExceptCatch(Context *pC, ClassRef class) { 180 | if (pC == NULL) pC = ExceptGetContext(NULL); 181 | if (pC->pEx->state == PENDING && ExceptIsDerived(pC->pEx->class, class)) 182 | pC->pEx->state = CAUGHT; 183 | return pC->pEx->state == CAUGHT; 184 | } 185 | 186 | /******** resolve at end of 'finally' ********/ 187 | int ExceptFinally(Context *pC) { 188 | Except * pEx, ex; 189 | 190 | if (pC == NULL) pC = ExceptGetContext(NULL); 191 | 192 | EXCEPT_THREAD_LOCK_FUNC(1); 193 | ex = *(pEx = LifoPop(pC->exStack)); 194 | free(pEx); 195 | pC->pEx = LifoCount(pC->exStack) ? LifoPeek(pC->exStack, 1) : NULL; 196 | EXCEPT_THREAD_LOCK_FUNC(0); 197 | 198 | if (LifoCount(pC->exStack) == 0) { /* outermost level - default action */ 199 | int rstrd = ExceptRestoreHandlers(pC); 200 | 201 | if (ex.state == PENDING) { 202 | if (ex.class == FailedAssertion) 203 | AssertAction(pC, DO_ABORT, ex.pData, ex.file, ex.line); 204 | else if (ExceptIsDerived(ex.class, RuntimeException) && rstrd) { 205 | EXCEPT_THREAD_LOCK_FUNC(1); 206 | LifoDestroy(pC->exStack); 207 | if (MULTI_THREADING) 208 | free(HashRemove(pContextHash, EXCEPT_THREAD_ID_FUNC())); 209 | else 210 | pC->exStack = NULL; 211 | EXCEPT_THREAD_LOCK_FUNC(0); 212 | raise(ex.class->signalNumber); 213 | } 214 | else if (ex.class == ReturnEvent) { 215 | EXCEPT_THREAD_LOCK_FUNC(1); 216 | LifoDestroy(pC->exStack); 217 | if (MULTI_THREADING) 218 | free(HashRemove(pContextHash, EXCEPT_THREAD_ID_FUNC())); 219 | else 220 | pC->exStack = NULL; 221 | EXCEPT_THREAD_LOCK_FUNC(0); 222 | longjmp(*(jmp_buf *)ex.pData, 1); 223 | } 224 | else 225 | fprintf(stderr, "%s lost: file \"%s\", line %d.\n", 226 | ex.class->name, ex.file, ex.line); 227 | } 228 | EXCEPT_THREAD_LOCK_FUNC(1); 229 | LifoDestroy(pC->exStack); 230 | if (MULTI_THREADING) 231 | free(HashRemove(pContextHash, EXCEPT_THREAD_ID_FUNC())); 232 | else 233 | pC->exStack = NULL; 234 | EXCEPT_THREAD_LOCK_FUNC(0); 235 | } 236 | else /* inner level - propagate */ 237 | if (ex.state == PENDING) 238 | if (ex.class == ReturnEvent && ex.first) 239 | longjmp(*(jmp_buf *)ex.pData, 1); /* pData is returnBuf */ 240 | else 241 | ExceptThrow(pC, ex.class, ex.pData, ex.file, ex.line); 242 | return 0; 243 | } 244 | 245 | /******** process 'return' ********/ 246 | void ExceptReturn(Context *pC) { 247 | if (pC == NULL) pC = ExceptGetContext(NULL); 248 | 249 | pC->pEx->class = ReturnEvent; /* may overrule pending exception */ 250 | pC->pEx->state = PENDING; /* in case of return() inside 'catch' */ 251 | longjmp(pC->pEx->finalBuf, 1); 252 | } 253 | 254 | /******** initiate 'catch' condition checking ********/ 255 | int ExceptCheckBegin(Context *pC, int *pChecked, char *file, int line) { 256 | if (pC->pEx->checkList == NULL && !*pChecked) { 257 | EXCEPT_THREAD_LOCK_FUNC(1); 258 | pC->pEx->checkList = ListCreate(); 259 | EXCEPT_THREAD_LOCK_FUNC(0); 260 | } 261 | else { 262 | if (!*pChecked) { 263 | if (ListCount(pC->pEx->checkList) == 0) { 264 | fprintf(stderr, 265 | "Warning: No catch clause(s): file \"%s\", line %d.\n", 266 | file, line); 267 | } 268 | EXCEPT_THREAD_LOCK_FUNC(1); 269 | ListDestroyData(pC->pEx->checkList); 270 | pC->pEx->checkList = NULL; 271 | *pChecked = 1; 272 | EXCEPT_THREAD_LOCK_FUNC(0); 273 | } 274 | } 275 | return *pChecked; 276 | } 277 | 278 | /******** perform 'catch' condition check ********/ 279 | int ExceptCheck(Context *pC, int *pChecked, ClassRef class, 280 | char *file, int line) { 281 | typedef struct _Check { 282 | ClassRef class; 283 | int line; 284 | } Check; 285 | 286 | if (!*pChecked) { 287 | Check * pCheck = ListHead(pC->pEx->checkList) 288 | 289 | while (pCheck != NULL) { /* breaked from */ 290 | if (class == pCheck->class) { 291 | fprintf(stderr, "Duplicate catch(%s): file \"%s\", line %d; " 292 | "already caught at line %d.\n", 293 | class->name, file, line, pCheck->line); 294 | break; 295 | } 296 | if (ExceptIsDerived(class, pCheck->class)) { 297 | fprintf(stderr, "Superfluous catch(%s): file \"%s\", line %d" 298 | "; already caught by %s at line %d.\n", class->name, 299 | file, line, pCheck->class->name, pCheck->line); 300 | break; 301 | } 302 | pCheck = ListNext(pC->pEx->checkList); 303 | } 304 | if (pCheck == NULL) { 305 | EXCEPT_THREAD_LOCK_FUNC(1); 306 | pCheck = malloc(sizeof(Check)); 307 | pCheck->class = class; 308 | pCheck->line = line; 309 | ListAddTail(pC->pEx->checkList, pCheck); 310 | EXCEPT_THREAD_LOCK_FUNC(0); 311 | } 312 | } 313 | return *pChecked; 314 | } 315 | -------------------------------------------------------------------------------- /Except.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Except.h - exception handling module header 3 | * 4 | * DESCRIPTION 5 | * This header contains the (type) definitions and more importantly, the 6 | * exeption handling support macros. It also defines return() as a macro. 7 | * 8 | * The 'try', 'catch' and 'finally' macro code has been nicely indented. 9 | * A 'try' starts a while-loop that end in its mandatory 'finally'. This 10 | * loop encloses zero or more 'catches'. When no 'catch' checking is 11 | * performed (i.e., when DEBUG is not defined), this loop is executed 12 | * twice. In the first pass the and longjmp() 13 | * destinations of the current exception object pEx> are saved; no 14 | * user code is executed. This is controlled by the flag pEx->ready> 15 | * which is initially zero and is set to one at the end of the first pass. 16 | * When DEBUG is defined an early pass is added which checks the 'catch' 17 | * conditions; nothing else happens during this pass. 18 | * 19 | * In the final pass (i.e., the second or third, depending on DEBUG), the 20 | * user code in the 'try' block is executed (pEx->ready> is 1 by 21 | * then). If no exception occurs the 'catch' clauses are fully skipped 22 | * and the 'break' halfway 'finally' is executed. If however an exception 23 | * occurred, a longjmp() to pEx->throwBuf> is performed (by the code 24 | * in "Except.c"). Because "setjmp(pC->pEx->throwBuf) == 0" in 'try' is 25 | * false, the "else if()" statement for each 'catch' are executed one by 26 | * one until a match takes place, causing ExceptCatch() to return 1. 27 | * After catching, the 'break' is also executed. 28 | * 29 | * Finally, the two nested while-statements are executed. At the start, 30 | * pEx->ready> is still 1 and thus greater than 0, so the inner 31 | * while-statement will be excecuted; ExceptFinally() is not invoked yet. 32 | * In the inner while-statement pEx->ready> is found to be greater 33 | * than 0, so the user [compound] statement below will be executed; but 34 | * first pEx->ready> is decremented and becomes 0. After the user 35 | * code is executed, the condition of the outer while-statement is again 36 | * evaluated. This time ExceptFinally() is invoked and always returns 0, 37 | * this ends the current exception handling level. 38 | * 39 | * REMARKS 40 | * The "do { } while (0);" are placed around the 'try' and 'catch' user 41 | * code to allow the user code contain 'break' and 'continue' statements. 42 | * Without it, the exception handling "while (1)" main loop would other- 43 | * wise be breaked or continued. 44 | * 45 | * For the same reason the while-loop that encloses the 'finally' code 46 | * has been split up in two to make sure that ExceptFinally() is always 47 | * invoked, even when the 'finally' user code does a 'break'. The reason 48 | * for checking "ready > 0" instead of "ready != 0" as is done in 'try' 49 | * and 'catch', is that becomes -1 when the user code does a 50 | * 'continue'. 51 | * 52 | * Most conditions in the macro code depend on ANSI C's left-to-right 53 | * lazy boolean expression evaluation. 54 | * 55 | * The reason why the setjmp() calls need to be placed in macros, is that 56 | * (according to the C standard) a longjmp() may only be done to a still 57 | * existing stack frame. 58 | * 59 | * K&R (2nd edition) on page 254 states that: "Accessible objects have the 60 | * values they had when longjmp() was called, except that non-volatile 61 | * automatic variables in the function calling setjmp() become undefined 62 | * if they were changed after the setjmp() call.". In our case this only 63 | * applies for , which is not modified, and so does not need to be 64 | * declared volatile. Be warned that this limitation does still exist for 65 | * application variables! 66 | * 67 | * COPYRIGHT 68 | * You are free to use, copy or modify this software at your own risk. 69 | * 70 | * AUTHOR 71 | * Cornelis van der Bent. Please let me know if you have comments or find 72 | * flaws: cg_vanderbent@mail.com. Enjoy! 73 | * 74 | * MODIFICATION HISTORY 75 | * 2000/03/23 vdbent Added 'pending'. 76 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 77 | * 1998/12/18 vdbent Conception. 78 | */ 79 | 80 | #ifndef _EXCEPT_H 81 | #define _EXCEPT_H 82 | 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include "Lifo.h" 88 | #include "List.h" 89 | 90 | #define SETJMP(env) sigsetjmp(env, 1) 91 | #define LONGJMP(env, val) siglongjmp(env, val) 92 | #define JMP_BUF sigjmp_buf 93 | 94 | 95 | typedef void (* Handler)(int); 96 | 97 | typedef struct _Class *ClassRef; /* exception class reference */ 98 | struct _Class 99 | { 100 | int notRethrown; /* always 1 (used by throw()) */ 101 | ClassRef parent; /* parent class */ 102 | char * name; /* this class name string */ 103 | int signalNumber; /* optional signal number */ 104 | }; 105 | 106 | typedef struct _Class Class[1]; /* exception class */ 107 | 108 | typedef enum _Scope /* exception handling scope */ 109 | { 110 | OUTSIDE = -1, /* outside any 'try' */ 111 | INTERNAL, /* exception handling internal */ 112 | TRY, /* in 'try' (across routine calls) */ 113 | CATCH, /* in 'catch' (idem.) */ 114 | FINALLY /* in 'finally' (idem.) */ 115 | } Scope; 116 | 117 | typedef enum _State /* exception handling state */ 118 | { 119 | EMPTY, /* no exception occurred */ 120 | PENDING, /* exception occurred but not caught */ 121 | CAUGHT /* occurred exception caught */ 122 | } State; 123 | 124 | typedef struct _Except /* exception handle */ 125 | { 126 | int notRethrown; /* always 0 (used by throw()) */ 127 | State state; /* current state of this handle */ 128 | JMP_BUF throwBuf; /* start-'catching' destination */ 129 | JMP_BUF finalBuf; /* perform-'finally' destination */ 130 | ClassRef class; /* occurred exception class */ 131 | void * pData; /* exception associated (user) data */ 132 | char * file; /* exception file name */ 133 | int line; /* exception line number */ 134 | int ready; /* macro code control flow flag */ 135 | Scope scope; /* exception handling scope */ 136 | int first; /* flag if first try in function */ 137 | List * checkList; /* list used by 'catch' checking */ 138 | char* tryFile; /* source file name of 'try' */ 139 | int tryLine; /* source line number of 'try' */ 140 | 141 | ClassRef (*getClass)(void); /* method returning class reference */ 142 | char * (*getMessage)(void); /* method getting description */ 143 | void * (*getData)(void); /* method getting application data */ 144 | void (*printTryTrace)(FILE*);/* method printing nested trace */ 145 | } Except; 146 | 147 | typedef struct _Context /* exception context per thread */ 148 | { 149 | Except * pEx; /* current exception handle */ 150 | Lifo * exStack; /* exception handle stack */ 151 | char message[1024]; /* used by ExceptGetMessage() */ 152 | Handler sigAbrtHandler; /* default SIGABRT handler */ 153 | Handler sigFpeHandler; /* default SIGFPE handler */ 154 | Handler sigIllHandler; /* default SIGILL handler */ 155 | Handler sigSegvHandler; /* default SIGSEGV handler */ 156 | Handler sigBusHandler; /* default SIGBUS handler */ 157 | } Context; 158 | 159 | extern Context * pC; 160 | extern Class Throwable; 161 | 162 | #define except_class_declare(child, parent) extern Class child 163 | #define except_class_define(child, parent) Class child = { 1, parent, #child } 164 | 165 | except_class_declare(Exception, Throwable); 166 | except_class_declare(OutOfMemoryError, Exception); 167 | except_class_declare(FailedAssertion, Exception); 168 | except_class_declare(RuntimeException, Exception); 169 | except_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */ 170 | except_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */ 171 | except_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */ 172 | except_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */ 173 | except_class_declare(BusError, RuntimeException); /* SIGBUS */ 174 | 175 | 176 | #ifdef DEBUG 177 | 178 | #define CHECKED \ 179 | static int checked 180 | 181 | #define CHECK_BEGIN(pC, pChecked, file, line) \ 182 | ExceptCheckBegin(pC, pChecked, file, line) 183 | 184 | #define CHECK(pC, pChecked, class, file, line) \ 185 | ExceptCheck(pC, pChecked, class, file, line) 186 | 187 | #define CHECK_END \ 188 | !checked 189 | 190 | #else /* DEBUG */ 191 | 192 | #define CHECKED 193 | #define CHECK_BEGIN(pC, pChecked, file, line) 1 194 | #define CHECK(pC, pChecked, class, file, line) 1 195 | #define CHECK_END 0 196 | 197 | #endif /* DEBUG */ 198 | 199 | 200 | #define except_thread_cleanup(id) ExceptThreadCleanup(id) 201 | 202 | #define try \ 203 | ExceptTry(pC, __FILE__, __LINE__); \ 204 | while (1) \ 205 | { \ 206 | Context * pTmpC = ExceptGetContext(pC); \ 207 | Context * pC = pTmpC; \ 208 | CHECKED; \ 209 | \ 210 | if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) && \ 211 | pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0) \ 212 | { \ 213 | pC->pEx->scope = TRY; \ 214 | do \ 215 | { 216 | 217 | #define catch(class, e) \ 218 | } \ 219 | while (0); \ 220 | } \ 221 | else if (CHECK(pC, &checked, class, __FILE__, __LINE__) && \ 222 | pC->pEx->ready && ExceptCatch(pC, class)) \ 223 | { \ 224 | Except *e = LifoPeek(pC->exStack, 1); \ 225 | pC->pEx->scope = CATCH; \ 226 | do \ 227 | { 228 | 229 | #define finally \ 230 | } \ 231 | while (0); \ 232 | } \ 233 | if (CHECK_END) \ 234 | continue; \ 235 | if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0) \ 236 | pC->pEx->ready = 1; \ 237 | else \ 238 | break; \ 239 | } \ 240 | ExceptGetContext(pC)->pEx->scope = FINALLY; \ 241 | while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC)) \ 242 | while (ExceptGetContext(pC)->pEx->ready-- > 0) 243 | 244 | #define throw(pExceptOrClass, pData) \ 245 | ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__) 246 | 247 | #define return(x) \ 248 | { \ 249 | if (ExceptGetScope(pC) != OUTSIDE) \ 250 | { \ 251 | void * pData = malloc(sizeof(JMP_BUF)); \ 252 | ExceptGetContext(pC)->pEx->pData = pData; \ 253 | if (SETJMP(*(JMP_BUF *)pData) == 0) \ 254 | ExceptReturn(pC); \ 255 | else \ 256 | free(pData); \ 257 | } \ 258 | return x; \ 259 | } 260 | 261 | #define pending \ 262 | (ExceptGetContext(pC)->pEx->state == PENDING) 263 | 264 | extern Scope ExceptGetScope(Context *pC); 265 | extern Context *ExceptGetContext(Context *pC); 266 | extern void ExceptThreadCleanup(int threadId); 267 | extern void ExceptTry(Context *pC, char *file, int line); 268 | extern void ExceptThrow(Context *pC, void * pExceptOrClass, 269 | void *pData, char *file, int line); 270 | extern int ExceptCatch(Context *pC, ClassRef class); 271 | extern int ExceptFinally(Context *pC); 272 | extern void ExceptReturn(Context *pC); 273 | extern int ExceptCheckBegin(Context *pC, int *pChecked, 274 | char *file, int line); 275 | extern int ExceptCheck(Context *pC, int *pChecked, ClassRef class, 276 | char *file, int line); 277 | 278 | 279 | #endif /* _EXCEPT_H */ 280 | -------------------------------------------------------------------------------- /doc/text: -------------------------------------------------------------------------------- 1 | Title: Let's Finally Try to Catch C 2 | Sub: Centralized exception handling in C 3 | By: Cornelis van der Bent 4 | 5 | 6 | Kees recently developed a generic RISC pipeline optimizer and added reverse 7 | engineering to a graphical hardware design tool. He loves making complex 8 | things easy and is interested in using (simple lateral) thinking techniques 9 | during the software development process. Contact him at vdbent@support.nl. 10 | ------------------------------------------------------------------------------- 11 | 12 | Well written C code has to deal with four kinds of anomalies: regular failures 13 | (e.g., unable to open file), out-of-memory conditions, run-time error signals 14 | (like SIGSEGV), and failed assertions. Traditionally, each is handled in a 15 | different way: returning error codes, exiting, using setjmp()/longjmp(), and 16 | printing a message followed by abort() respectively. Furthermore, C does not 17 | support centralized exception handling like C++ and Java do. This all results 18 | in functional code getting cluttered with error handling statements. 19 | 20 | While working on a recursive-descent parser in C, I needed a mechanism to skip 21 | any number of stack frames for easy error handling. The simple try, catch and 22 | throw macros I created using setjmp()/longjmp() worked fine, but did not allow 23 | nesting and only addressed one kind of errors. Soon after, I took up the 24 | challenge to create a complete centralized exception handling package in plain 25 | ANSI C, that looks like Java (and C++) in the best possible way, is easy to use 26 | and has as little side-effects as possible. 27 | 28 | Looking at the result, I am quite satisfied. The package as it is now, offers 29 | many of Java's features and code using it looks just like C++ and Java. It even 30 | has important features not found in C++: supplying throw file location and 31 | catching signals. To get a taste see Example 1 and Figure 1. In this article I 32 | will show you how to use the package, describing every feature in detail. Also, 33 | I will explain some interesting aspects of the implementation. Even if you will 34 | never use this package, reading this article can be of help for better 35 | understanding of centralized exception handling in C++ and Java. 36 | 37 | 38 | Catching 39 | -------- 40 | When an exception occurs inside a try statement, its catch clauses are tried 41 | from top to bottom. If the thrown exception is equal to, or a subclass of 42 | the first argument of the catch() macro, then its code is executed. An 43 | exception is caught only by the first matching catch clause. 44 | 45 | As second argument you must supply the identifier name by which the exception 46 | can be referenced in the catch code. The Except structure (see Listing One), 47 | to which you get a pointer, currently has three access member functions: 48 | * You can use getClass() to find out which subclass you actually caught as in 49 | Example 2. 50 | * Then, getMessage() returns the address of a (per thread) static string 51 | containing the exception class name and where thrown (file name & line 52 | number). 53 | * Thirdly, getData() hands you the data pointer that was supplied as second 54 | argument of the corresponding throw(). This allows you to pass some data from 55 | critical code to exception handling code. For a FailedAssertion it points to 56 | a string containing the failed/false expression. 57 | 58 | Notice that you don't supply these member functions a pointer to the Except 59 | structure, as you would expect to be needed in C. I used the anyhow needed 60 | mechanism for looking up the internal exception handling data for the current 61 | thread and its try statement nesting level, as explained in Figure 2. To 62 | achieve Java likeness, I took the extra delay caused by the, yet very fast, 63 | (hash table) lookup for granted. 64 | 65 | 66 | Catch Checking 67 | -------------- 68 | Java and C++ compilers nicely notify you, when catching the same exception 69 | class more than once (Example 3), and when a subclass is caught below its 70 | baseclass (Example 4). Although not at compile-time, my package does offer 71 | this same functionality at run-time, as a '-DDEBUG option'. Unfortunately 72 | a try statement is only checked when it is executed. 73 | 74 | You will see from the macro definitions in Listing One, that the catch clauses 75 | are enclosed by a while-loop that starts in try and ends in finally. Normally 76 | this loop is executed twice, with catch checking a third early pass is added. 77 | During this pass the ExceptCheck() (Listing Two) routine is invoked for each 78 | catch clause. All user code is skipped. Because the flag named checked, defined 79 | inside the loop, is static, each try statement will only be checked once by the 80 | first thread executing it. When DEBUG is not defined, the compiler will 81 | optimize away all that's left from the macros used; so there is no persistent 82 | performance loss. 83 | 84 | 85 | Exception Class Hierarchy 86 | ------------------------- 87 | As in OO languages, you can define a tree hierarchy of exception classes. The 88 | package defines a number of classes it uses for the standard exceptions, as 89 | shown in Figure 3. To build your own class tree(s), you will typically extend 90 | the standard Throwable and Exception classes. The nice thing is that there are 91 | no limits! Defining a new class is done with ex_class_define(child,parent) as 92 | you can see in Example 1. Just like with C variables, you may place a class 93 | declaration ex_class_declare(child,parent) in a common header file when you 94 | want to share it between modules. The inheritance mechanism works across 95 | compiled modules, so only the declaration needs to be in scope in order to 96 | extend, catch (itself or one of its subclasses) or throw an exception class. 97 | 98 | I particularly like this feature myself: (Artificial) Inheritance in such a 99 | simple way. The ex_class_define(child,parent) macro expands to the definition 100 | of a variable named child, of type Class, and includes its initializer. This 101 | initializer makes the Class structure member called parent, point to its 102 | parent. In this way each individual exception class is linked to its parent, 103 | resulting in a DAG (directed acyclic graph). All ancestors of any class can be 104 | found by following the links, which is exactly what the catching code 105 | (ExceptCatch() in Listing Two) needs. Figure 4 gives an overview. 106 | 107 | Note that adding an inheritance level, on average slows down catching a little 108 | because the links are followed sequentially. 109 | 110 | 111 | Throwing 112 | -------- 113 | Using the throw() macro you can cause an exception. As first argument you 114 | specify the exception class, using the second argument you can pass a void 115 | pointer to your exception handling code (see "Catching" above). An exception 116 | can be thrown from any scope: 117 | * Outside any try statement: reported as being lost (the default action for 118 | user exceptions). 119 | * Inside try: caught by one of its catch clauses, otherwise propagated. 120 | * Inside catch: propagated, but finally is executed first. 121 | * Inside finally: overrides any uncaught exception and itself is propagated. 122 | 123 | Note that a throw works across function call boundaries, so with 'inside try' 124 | for example, I mean in the immediate context of the try or from a function 125 | (indirectly) called from this context (see Example 5). Java behaves in exactly 126 | the same way [ARN97]. 127 | 128 | All the throw() macro does, is invoking ExceptThrow() from Listing Two. 129 | Depending on the scope, it either prints a message ('outside'), or performs a 130 | non-local goto using the ANSI C longjmp() back to the expanded try code just 131 | before the catch clauses ('inside try') or the expanded finally ('inside catch' 132 | or 'inside finally'). In order to be able to jump forwards to the expanded 133 | finally, the while-loop starting in try and ending in the finally macro code, 134 | is executed once before any user code, just to save the longjmp() destination 135 | buffer finalBuf. This is done using the ANSI C setjmp(). Study Figure 5 if you 136 | want to get a grip on how this all works. 137 | 138 | For a short introduction to setjmp()/longjmp() turn to worth buying [LIB93]. 139 | 140 | 141 | Re-throwing 142 | ---------- 143 | Inside a catch clause you can throw the just caught exception again to let 144 | it propagate to a higher exception handling level. Example 2 shows how this 145 | is done. 146 | 147 | This means that throw() can handle either a ClassRef, or a pointer to an Except 148 | structure (Listing One). The underlying routine ExceptThrow() can keep them 149 | apart by looking at the notRethrown member that both datastructures have in 150 | common. The value of notRethrow is always zero in Except structures and always 151 | one in exception Classes. Apart from minor differences, a re-throw works in the 152 | same way as a regular throw inside a catch clause. 153 | 154 | 155 | Propagation, Overruling, Nesting and Recursion 156 | ---------------------------------------------- 157 | When an exception is not caught at the exception handling level it occurred, 158 | it is propagated upwards to the enclosing level. As you can see in Figure 5, 159 | propagation involves simply re-throwing the exception right after try statement 160 | it came from. Depending on the scope, one of four actions is performed as 161 | described in "Throwing" above. When the new scope is outside, the default 162 | action for each type of exception is performed: 163 | * User thrown or out-of-memory: report it as being lost. 164 | * Failed assertion: print standard assert() message and abort() (if configured). 165 | * Signal: raise() it. 166 | 167 | Propagation continues until the pending exception is caught or enters the 168 | outside scope. It is important to know that during propagation the finally 169 | code block of every visited level is executed; this also applies when you 170 | return(), but only up to the outermost level of the current routine. Because 171 | the finally user code is executed prior to propagation, it can overrule a 172 | pending exception or return. Example 6 shows you what this means. 173 | 174 | A try statement may appear, with unlimited nesting, in each of the three 175 | blocks: try, catch and finally. Propagation works properly in all cases. 176 | Propagation also works fine for recursive functions (Example 10). 177 | 178 | Especially when looking at Figure 5, you can imagine that it took quite some 179 | effort to get the package behave correctly and exactly as Java [ARN97], for 180 | the many different scenarios. 181 | 182 | 183 | Returning 184 | --------- 185 | To allow propagation of a return (see "Propagation and Overruling" above), I 186 | defined the return() macro having the required behavior (Listing One). Although 187 | I think that overruling a C keyword is not nice, here it does not hurt much 188 | (when you are aware of it). 189 | 190 | Because return was defined as a macro with a single argument, you can still 191 | refer to the native return simply by not using parentheses. In the (rare) 192 | occasions that the returned expression starts with something between 193 | parentheses, for example a cast: 194 | 195 | return (int *)pData; /* compile-time error */ 196 | 197 | you can use the comma operator to prevent the preprocessor recognizing the 198 | return() macro: 199 | 200 | return 0, (int *)pData; /* OK */ 201 | 202 | The only 'real problem' that remains, is a relatively small amount of overhead. 203 | 204 | 205 | Break and Continue 206 | ------------------ 207 | The break and continue statements used in a try, catch or finally (of course 208 | outside a while, for or switch statement) work differently from Java. 209 | Performing a break or continue inside a try or catch starts executing the 210 | finally. Inside a finally the remaining part of your finally code is simply 211 | skipped. Propagation of a pending exception or return is not affected. 212 | 213 | When you place a try statement in for example a while loop in Java, you can 214 | use break to leave the loop (finally is first executed), or use continue 215 | to skip to the next iteration (finally code also first executed). If you need 216 | this functionality, you can use the workaround shown in Example 7, or 217 | restructure your code. 218 | 219 | 220 | Assertion Checking 221 | ------------------ 222 | The header "Assert.h" supplied with this package, defines the assert() macro 223 | which performs a throw() on failure. You can catch failed assertions with the 224 | FailedAssertion exception class. The string representation of the failed (i.e., 225 | false) expression can be obtained with the caught exception member function 226 | getData(). 227 | 228 | I experienced that, when building fault tolerant code, it can be very handy to 229 | have a macro that works as assert() during DEBUGging, but returns on failure 230 | during field operation. For this purpose I created a macro validate(). See 231 | Example 8. It has also helped me finding the cause of a failed assertion, by 232 | letting assert() use a function, which allows placing a breakpoint. The 233 | standard C assert() does not have this feature. 234 | 235 | I highly recommend Writing Solid Code by Steve Maguire [MAG93], it marvelously 236 | covers the subject of assertion checking and bug finding in general. 237 | 238 | 239 | Memory Allocation 240 | ----------------- 241 | This package supplies an optional header file that replaces the standard 242 | memory allocation routines with macros. These macros use routines, from the 243 | corresponding module, which invoke the native C library routines, but throw 244 | an exception when out of memory. Example 9 shows the macro and corresponding 245 | routine of one of them. (You may want to use a different name for the macros.) 246 | 247 | 248 | Signal Handling 249 | --------------- 250 | As an additional feature compared to C++, the package delivers synchronous 251 | signals as any other exception (Sidebar 1). The following ANSI C defined 252 | [KER88] signals are supported: 253 | * SIGABRT, results in AbnormalTermination. 254 | * SIGFPE, results in ArithmeticException. 255 | * SIGILL, results in IllegalInstruction. 256 | * SIGSEGV, results in SegmentationFault. 257 | 258 | Any of these four exception classes can be caught with their parent class 259 | RuntimeException (Figure 3 and Example 10) 260 | 261 | As part of executing an outermost try, the same handler (ExceptThrowSignal() 262 | in Listing Two) is installed for all four signals. The replaced (default) 263 | handlers are stored. 264 | 265 | Basicly, all the handler does is throwing the exception class that corresponds 266 | to the received signal. Subsequently, ExceptThrow() performs a longjmp(), at 267 | which the signal handling context is left (Figure 5). 268 | 269 | During an outermost finally, the signal handlers stored during the outermost 270 | try will be restored again. In a multi-threading environment the threads 271 | either share the signal handlers or each have their private set. With shared 272 | handlers, restoration is delayed until the final thread leaves its outermost 273 | finally. Selection between shared or private handlers is done at compile-time, 274 | with a single preprocessor flag. 275 | 276 | 277 | Multi-threading 278 | --------------- 279 | This package allows multiple threads, having a common address space, to 280 | concurrently use exception handling. The offered functionality is exactly the 281 | same as described so far. The internal exception context, that has to be kept 282 | for each thread (Figure 2), is conveniently hidden; you are not confronted with 283 | obscure pointers as you would expect to see in a C implementation. 284 | 285 | The package code is platform independent and fully ANSI C. Yet, you must supply 286 | only two platform specific functions: 287 | * A function having no arguments that returns the unique int(eger) ID of the 288 | current thread. As Figure 2 shows you, the thread ID is used to look up the 289 | exception context of the current thread. Most platforms have a function like 290 | this you can use right away (I know of Solaris, POSIX, VxWorks and NT). 291 | * A function with a single boolean argument and no return value, that can 292 | disable/lock (argument is 1) and enable/unlock (argument is 0) thread 293 | scheduling. Because the threads share data, the package needs to mutual 294 | exclude different threads modifying this data. Additionally a thread may 295 | only kill another thread when it holds the lock, otherwise the shared data 296 | might be left in an undefined state by the killed thread. 297 | Some platforms supply functions that directly disable/enable scheduling 298 | (Example 11). On other platforms you can use a mutual exclusion object 299 | (Example 12). 300 | 301 | When a thread kills another thread it must call ex_thread_cleanup() with the 302 | ID of the killed thread. This prevents a memory leak and in case of shared 303 | signal handlers makes sure that the default signal handlers are restored when 304 | the last thread leaves its outermost finally. 305 | 306 | 307 | Conclusion 308 | ---------- 309 | Of course many others have used setjmp()/longjmp() to create more or less 310 | sophisticated exception handling mechanisms. One of them is even described in 311 | a book [HAN97]. Although I have never seen nor heard of a package that comes 312 | close to what I have created, I am very curious to see other implementations 313 | and learn from them. Also, when you have comments on my implementation, please 314 | let me know. 315 | 316 | Crafting this package was really exciting and fun. I hope you will enjoy it 317 | and find it useful. The latest version is available from www.support.nl/~shape. 318 | 319 | 320 | References 321 | ---------- 322 | [ARN97] Arnold, K., Gosling, J. 1997. The Java Programming Language, second 323 | edition. Addison-Wesley, Reading, MA. 324 | [HAN97] Hanson, D.R. 1997. C Interfaces and Implementations, techniques for 325 | creating reusable software. Addison-Wesley, Reading, MA. 326 | [KER88] Kernigham, B.W., Ritchie, D.M. 1988. The C Programming Language, second 327 | edition. Prentice Hall, Englewood Cliffs, NJ. 328 | [LIB93] Libes, D. 1993. Obfuscated C and Other Mysteries. John Wiley & Sons, 329 | New York. 330 | [MAG93] Maguire, S. 1993. Writing Solid Code. Microsoft Press, ... 331 | [SOL26] Sun Microsystems 1997. Solaris 2.6 on-line manual pages. Mountain View, 332 | CA. 333 | [STR97] Stroustrup, B. 1997. The C++ Programming Language, third edition. 334 | Addison-Wesley, Reading, MA. 335 | [VXW51] Wind River Systems 1993. VxWorks Reference Manual 5.1. Alamada, CA. 336 | 337 | 338 | DDJ 339 | -------------------------------------------------------------------------------- /Test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Test.c - tests for C exception handling package (single threaded) 3 | * 4 | * DESCRIPTION 5 | * This program contains tens of test routines for the Java like 6 | * exception handling package for C. It is impossible to fully 7 | * automate all the tests. In this version of the program the 8 | * result of all tests needs to be verifid by visual inspection of 9 | * the output. For each test the programs prints the expected 10 | * result, which will be printed immediately after. 11 | * 12 | * TODO 13 | * Badly needs to be updated (and cleaned up) to cover all features 14 | * and check all functionality boundaries. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "Except.h" 22 | #include "Alloc.h" 23 | #ifndef DEBUG 24 | #define DEBUG /* switch on assertion checking */ 25 | #endif 26 | #include "Assert.h" 27 | 28 | except_class_declare(Level1Exception, Exception); 29 | except_class_declare(Level2Exception, Level1Exception); 30 | except_class_define(Level1Exception, Exception); 31 | except_class_define(Level2Exception, Level1Exception); 32 | 33 | int testNum = 1; 34 | 35 | int main(void); 36 | 37 | static void TestThrow(void) 38 | { 39 | printf("\nTHROW TESTS -------------------------------------------\n\n"); 40 | 41 | /* see if exception is lost outside */ 42 | printf("-->%2d: Lost Exception?\n", testNum++); 43 | throw (Exception, NULL); 44 | printf("\n"); 45 | 46 | /* see if throw works and if correct catch clause is selected */ 47 | try 48 | { 49 | printf("-->%2d: Caught Exception?\n", testNum++); 50 | throw (Exception, NULL); 51 | } 52 | catch (RuntimeException, e); 53 | catch (Exception, e) 54 | { 55 | printf("%s\n", e->getMessage()); 56 | } 57 | finally; 58 | printf("\n"); 59 | 60 | /* see if throw works properly from within catch clause */ 61 | try 62 | { 63 | throw (Exception, NULL); 64 | } 65 | catch (Exception, e) 66 | { 67 | printf("-->%2d: Lost Level1Exception?\n", testNum++); 68 | throw (Level1Exception, NULL); 69 | } 70 | catch (Level1Exception, e) 71 | { 72 | printf("%s\n", e->getMessage()); 73 | } 74 | finally; 75 | printf("\n"); 76 | 77 | /* see if exception class inheritance works in the right direction */ 78 | try 79 | { 80 | printf("-->%2d: Lost Level1Exception?\n", testNum++); 81 | throw (Level1Exception, NULL); 82 | } 83 | catch (Level2Exception, e); 84 | finally; 85 | printf("\n"); 86 | 87 | /* again see if exception class inheritance works in the right direction */ 88 | try 89 | { 90 | printf("-->%2d: Caught Level2Exception?\n", testNum++); 91 | throw (Level2Exception, NULL); 92 | } 93 | catch (Level1Exception, e) 94 | { 95 | printf("%s\n", e->getMessage()); 96 | } 97 | finally; 98 | printf("\n"); 99 | 100 | try; 101 | finally 102 | { 103 | printf("-->%2d: Lost Exception?\n", testNum++); 104 | throw (Exception, NULL); 105 | } 106 | printf("\n"); 107 | } 108 | 109 | int t1(void) 110 | { 111 | try 112 | { 113 | return(6); 114 | } 115 | catch (Throwable, e) 116 | { 117 | printf("%s\n", e->getMessage()); 118 | } 119 | finally; 120 | return(7); 121 | } 122 | 123 | int t2(void) 124 | { 125 | try 126 | { 127 | return(6); 128 | } 129 | finally 130 | { 131 | return(7); 132 | } 133 | } 134 | 135 | int t3(void) 136 | { 137 | try 138 | { 139 | assert(0); 140 | } 141 | catch (FailedAssertion, e) 142 | { 143 | return(8); 144 | } 145 | finally; 146 | } 147 | 148 | int t4(void) 149 | { 150 | try 151 | { 152 | assert(0); 153 | } 154 | catch (FailedAssertion, e) 155 | { 156 | return(8); 157 | } 158 | finally 159 | { 160 | return(9); 161 | } 162 | } 163 | 164 | int tX(void) 165 | { 166 | try 167 | { 168 | try 169 | { 170 | try 171 | { 172 | return(1); 173 | } 174 | finally 175 | { 176 | printf("A "); 177 | } 178 | } 179 | finally 180 | { 181 | printf("B "); 182 | } 183 | } 184 | finally 185 | { 186 | printf("C "); 187 | } 188 | 189 | return 2; 190 | } 191 | 192 | int spell(void) 193 | { 194 | if (1) 195 | try 196 | try 197 | try 198 | throw (Throwable, 0); 199 | catch (Throwable, e) 200 | return(1); 201 | finally 202 | { 203 | printf("A"); 204 | return(2); 205 | } 206 | 207 | catch (Throwable, e) 208 | printf("Magic"); 209 | finally 210 | printf("B"); 211 | 212 | finally 213 | printf("C"); 214 | return(3); 215 | } 216 | 217 | static int TestReturn(void) 218 | { 219 | printf("\nRETURN TESTS ------------------------------------------\n\n"); 220 | 221 | printf("-->%2d: Returns 6?\n", testNum++); 222 | printf("Return value = %d\n", t1()); 223 | printf("\n"); 224 | 225 | printf("-->%2d: Returns 7?\n", testNum++); 226 | printf("Return value = %d\n", t2()); 227 | printf("\n"); 228 | 229 | printf("-->%2d: Returns 8?\n", testNum++); 230 | printf("Return value = %d\n", t3()); 231 | printf("\n"); 232 | 233 | printf("-->%2d: Returns 9?\n", testNum++); 234 | printf("Return value = %d\n", t4()); 235 | printf("\n"); 236 | 237 | printf("-->%2d: Prints \"ABC2\"?\n", testNum++); 238 | try 239 | { 240 | printf("%d\n", spell()); 241 | } 242 | catch (Throwable, e) 243 | printf("%s\n", e->getMessage()); 244 | finally; 245 | printf("\n"); 246 | 247 | printf("-->%2d: Prints \"A B C 1\"?\n", testNum++); 248 | printf("%d\n", tX()); 249 | printf("\n"); 250 | } 251 | 252 | static void t5(void) 253 | { 254 | try 255 | { 256 | raise(SIGABRT); 257 | } 258 | catch (SegmentationFault, e); 259 | catch (IllegalInstruction, e); 260 | finally; 261 | } 262 | 263 | static void t6(void) 264 | { 265 | try 266 | { 267 | *((int *)0) = 0; /* may not be portable */ 268 | } 269 | catch (SegmentationFault, e) 270 | { 271 | long f[] = { 1, 2, 3 }; 272 | ((void(*)())f)(); /* may not be portable */ 273 | } 274 | finally 275 | { 276 | } 277 | } 278 | 279 | static int t7(void) 280 | { 281 | #if 0 282 | try 283 | { 284 | *((int *)0) = 0; /* may not be portable */ 285 | } 286 | catch (SegmentationFault, e) 287 | { 288 | long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' }; 289 | ((void(*)())f)(); /* may not be portable */ 290 | } 291 | finally 292 | { 293 | return(1 / strcmp("", "")); 294 | } 295 | #else 296 | static double n; 297 | 298 | return 0.0 / n; 299 | #endif 300 | } 301 | 302 | static int t8(void) 303 | { 304 | try 305 | { 306 | } 307 | finally 308 | { 309 | *((int *)0) = 0; 310 | } 311 | } 312 | 313 | static void TestSignal(void) 314 | { 315 | printf("\nSIGNAL TESTS ------------------------------------------\n\n"); 316 | 317 | try 318 | { 319 | char* p = "segment"; 320 | printf("-->%2d: Violates segmentation?\n", testNum++); 321 | 322 | while (1) 323 | { 324 | *p++ = ' '; 325 | } 326 | 327 | //*((long *)main) = 0; /* might not be portable */ 328 | } 329 | catch (SegmentationFault, e) 330 | { 331 | printf("%s\n", e->getMessage()); 332 | } 333 | catch (RuntimeException, e) 334 | { 335 | printf("%s\n", e->getMessage()); 336 | } 337 | finally; 338 | printf("\n"); 339 | 340 | try 341 | { 342 | printf("-->%2d: Aborts?\n", testNum++); 343 | t5(); 344 | } 345 | catch (AbnormalTermination, e) 346 | { 347 | printf("%s\n", e->getMessage()); 348 | } 349 | finally; 350 | printf("\n"); 351 | 352 | try 353 | { 354 | printf("-->%2d: Illegal instruction?\n", testNum++); 355 | t6(); 356 | } 357 | catch (RuntimeException, e) 358 | { 359 | printf("%s\n", e->getMessage()); 360 | } 361 | finally; 362 | printf("\n"); 363 | 364 | try 365 | { 366 | printf("-->%2d: Arithmetic?\n", testNum++); 367 | t7(); 368 | } 369 | catch (Throwable, e) 370 | { 371 | printf("%s\n", e->getMessage()); 372 | } 373 | finally; 374 | printf("\n"); 375 | 376 | try 377 | { 378 | printf("-->%2d: RuntimeException (in finally)?\n", testNum++); 379 | t8(); 380 | } 381 | catch (RuntimeException, e) 382 | { 383 | printf("%s\n", e->getMessage()); 384 | } 385 | finally; 386 | } 387 | 388 | static void TestMemory(void) 389 | { 390 | printf("\nMEMORY TESTS ------------------------------------------\n\n"); 391 | 392 | try 393 | { 394 | void * p; 395 | 396 | printf("-->%2d: Out of memory?\n", testNum++); 397 | p = malloc(INT_MAX); /* calloc() and realloc() are similar */ 398 | } 399 | catch (OutOfMemoryError, e) 400 | { 401 | printf("%s\n", e->getMessage()); 402 | } 403 | finally; 404 | printf("\n"); 405 | 406 | try 407 | { 408 | void * p; 409 | 410 | printf("-->%2d: Out of memory?\n", testNum++); 411 | p = new(int[INT_MAX/4]); 412 | } 413 | catch (OutOfMemoryError, e) 414 | { 415 | printf("%s\n", e->getMessage()); 416 | } 417 | finally; 418 | printf("\n"); 419 | 420 | try 421 | { 422 | void * p; 423 | 424 | printf("-->%2d: Not out of memory?\n", testNum++); 425 | p = new(int); 426 | } 427 | catch (OutOfMemoryError, e) 428 | { 429 | printf("%s\n", e->getMessage()); 430 | } 431 | finally 432 | { 433 | printf("Enough memory left.\n"); 434 | } 435 | printf("\n"); 436 | } 437 | 438 | static void TestNesting() 439 | { 440 | printf("\nNESTING TESTS -----------------------------------------\n\n"); 441 | 442 | try 443 | { 444 | try 445 | { 446 | try 447 | { 448 | printf("-->%2d: Throws Level2Exception?\n", testNum++); 449 | throw (Level2Exception, NULL); 450 | } 451 | catch (RuntimeException, e); 452 | catch (OutOfMemoryError, e); 453 | finally; 454 | } 455 | catch (FailedAssertion, e); 456 | finally; 457 | } 458 | catch (RuntimeException, e); 459 | catch (Exception, e) 460 | { 461 | printf("%s\n", e->getMessage()); 462 | } 463 | finally; 464 | printf("\n"); 465 | 466 | try 467 | { 468 | try 469 | { 470 | try 471 | { 472 | throw (Level2Exception, NULL); 473 | } 474 | catch (OutOfMemoryError, e); 475 | catch (RuntimeException, e); 476 | finally; 477 | } 478 | catch (Level2Exception, e) 479 | { 480 | try 481 | { 482 | printf("-->%2d: Throws Level1Exception?\n", testNum++); 483 | throw (Level1Exception, NULL); 484 | } 485 | catch (RuntimeException, e); 486 | finally; 487 | } 488 | finally; 489 | } 490 | catch (RuntimeException, e); 491 | catch (Level2Exception, e); 492 | catch (Level1Exception, e) 493 | { 494 | e->printTryTrace(0); 495 | } 496 | finally; 497 | printf("\n"); 498 | 499 | try 500 | { 501 | try 502 | { 503 | try 504 | { 505 | throw (Exception, NULL); 506 | } 507 | catch (Exception, e) 508 | { 509 | throw (Level1Exception, NULL); 510 | } 511 | catch (Level1Exception, e) 512 | { 513 | throw (Throwable, NULL); 514 | } 515 | finally; 516 | } 517 | catch (Level1Exception, e) 518 | { 519 | printf("-->%2d: Throws Level2Exception?\n", testNum++); 520 | throw (Level2Exception, NULL); 521 | } 522 | catch (Throwable, e) 523 | { 524 | throw (Throwable, NULL); 525 | } 526 | finally; 527 | } 528 | catch (Throwable, e) 529 | { 530 | printf("%s\n", e->getMessage()); 531 | } 532 | finally; 533 | printf("\n"); 534 | 535 | try 536 | { 537 | try 538 | { 539 | throw (Exception, NULL); 540 | } 541 | finally 542 | { 543 | printf("-->%2d: Throws Level1Exception?\n", testNum++); 544 | throw (Level1Exception, NULL); 545 | } 546 | } 547 | catch (Exception, e) 548 | { 549 | printf("%s\n", e->getMessage()); 550 | } 551 | finally; 552 | printf("\n"); 553 | 554 | try 555 | { 556 | printf("-->%2d: No Level1Exception caught?\n", testNum++); 557 | throw (Level1Exception, NULL); 558 | } 559 | catch (Level1Exception, e); 560 | catch (Level1Exception, e) 561 | { 562 | printf("%s\n", e->getMessage()); 563 | } 564 | finally 565 | { 566 | printf("Nothing caught.\n"); 567 | } 568 | printf("\n"); 569 | 570 | try 571 | try 572 | { 573 | printf("-->%2d: Rethrow test: prints \"Hello\"?\n", testNum++); 574 | throw (Exception, "Hello"); 575 | } 576 | catch (Exception, e) 577 | throw (e, "there!"); 578 | finally; 579 | catch (Exception, e) 580 | printf("%s\n", e->getData()); 581 | finally; 582 | printf("\n"); 583 | 584 | printf("-->%2d: Does nothing (except for some warnings)?\n", testNum++); 585 | try 586 | try 587 | try 588 | raise(SIGABRT); 589 | catch (Throwable, e); 590 | finally; 591 | finally; 592 | finally 593 | printf("Nothing!\n"); 594 | printf("\n"); 595 | 596 | printf("-->%2d: Does nothing (except for some warnings)?\n", testNum++); 597 | try 598 | try 599 | finally 600 | { /* required '{' */ 601 | try 602 | finally; 603 | } 604 | finally 605 | printf("Nothing!\n"); 606 | printf("\n"); 607 | } 608 | 609 | static void TestAssert(void) 610 | { 611 | printf("\nASSERT TESTS ------------------------------------------\n\n"); 612 | 613 | printf("-->%2d: Failed assert line %d?\n", testNum++, __LINE__ + 1); 614 | assert(0); 615 | printf("\n"); 616 | 617 | try 618 | { 619 | printf("-->%2d: Caught assert line %d?\n", testNum++, __LINE__ + 1); 620 | assert((0, 0, 0, 0, 0)); 621 | } 622 | catch (FailedAssertion, e) 623 | { 624 | printf("%s -- %s\n", e->getMessage(), e->getData()); 625 | } 626 | finally; 627 | printf("\n"); 628 | 629 | try 630 | { 631 | assert(0); 632 | } 633 | catch (FailedAssertion, e) 634 | { 635 | printf("-->%2d: Failed assert line %d?\n", testNum++, __LINE__ + 1); 636 | assert(0); 637 | } 638 | finally; 639 | printf("\n"); 640 | 641 | try; 642 | finally 643 | { 644 | printf("-->%2d: Failed assert line %d?\n", testNum++, __LINE__ + 1); 645 | assert(0); 646 | } 647 | printf("\n"); 648 | } 649 | 650 | static int t9(void) 651 | { 652 | #undef assert 653 | #define assert(e) /* as in "Assert.h" when DEBUG not defined */ 654 | 655 | validate(0, 27); 656 | } 657 | 658 | static void TestValidate(void) 659 | { 660 | printf("\nVALIDATE TESTS ----------------------------------------\n\n"); 661 | 662 | printf("-->%2d: Returns 27?\n", testNum++); 663 | printf("Returned %d\n", t9()); 664 | printf("\n"); 665 | } 666 | 667 | static void TestCheck(void) 668 | { 669 | printf("\nCHECK TESTS -------------------------------------------\n\n"); 670 | 671 | printf("-->%2d: Superfluous catch Exception at line %d?\n", testNum++, 672 | __LINE__ + 3); 673 | try; 674 | catch (Throwable, e); 675 | catch (Exception, e); 676 | finally; 677 | printf("\n"); 678 | 679 | printf("-->%2d: Superfluous catch Level2Exception at line %d?\n", testNum++, 680 | __LINE__ + 4); 681 | try; 682 | catch (Exception, e); 683 | catch (FailedAssertion, e); 684 | catch (Level2Exception, e); 685 | catch (RuntimeException, e); 686 | finally; 687 | printf("\n"); 688 | 689 | printf("-->%2d: Two superfluous catches?\n", testNum++); 690 | try; 691 | catch (Throwable, e); 692 | catch (FailedAssertion, e); 693 | catch (Exception, e); 694 | finally; 695 | printf("\n"); 696 | 697 | printf("-->%2d: Duplicate catch (SegmentationFault) at line %d?\n", 698 | testNum++, __LINE__ + 4); 699 | try; 700 | catch (SegmentationFault, e); 701 | catch (FailedAssertion, e); 702 | catch (SegmentationFault, e); 703 | catch (RuntimeException, e); 704 | finally; 705 | printf("\n"); 706 | 707 | printf("-->%2d: Warning: No catches?\n", testNum++); 708 | try; 709 | finally; 710 | printf("\n"); 711 | } 712 | 713 | void recurse(int x) 714 | { 715 | printf("recurse(%d)\n", x); 716 | 717 | try 718 | { 719 | if (x == 0) 720 | *((int *)0) = 0; 721 | recurse(x - 1); 722 | } 723 | finally 724 | printf("%d, ", x); 725 | } 726 | 727 | static void TestRecursion(void) 728 | { 729 | printf("\nRECURSION TESTS ---------------------------------------\n\n"); 730 | 731 | printf("-->%2d: Hits a run-time exception after 10 levels?\n", testNum++); 732 | try 733 | recurse(10); 734 | catch (RuntimeException, e) 735 | printf("%s\n", e->getMessage()); 736 | finally; 737 | printf("\n"); 738 | } 739 | 740 | 741 | void CheckStack(void) 742 | { 743 | Context *pC = ExceptGetContext(NULL); 744 | 745 | if (pC != NULL && pC->exStack != NULL) 746 | { 747 | printf("LifoCount == %d != 0\n", LifoCount(pC->exStack)); 748 | } 749 | } 750 | 751 | 752 | int main(void) 753 | { 754 | TestThrow(); 755 | CheckStack(); 756 | 757 | TestReturn(); 758 | CheckStack(); 759 | 760 | TestMemory(); 761 | CheckStack(); 762 | 763 | TestNesting(); 764 | CheckStack(); 765 | 766 | TestAssert(); 767 | CheckStack(); 768 | 769 | TestValidate(); 770 | CheckStack(); 771 | 772 | TestCheck(); 773 | CheckStack(); 774 | 775 | TestRecursion(); 776 | CheckStack(); 777 | 778 | TestSignal(); 779 | CheckStack(); 780 | 781 | printf("\nREADY\n\n"); 782 | } 783 | -------------------------------------------------------------------------------- /List.c: -------------------------------------------------------------------------------- 1 | /* 2 | * List.c - doubly linked list library 3 | * 4 | * DESCRIPTION 5 | * This module contains routines to create and maintain doubly linked 6 | * lists of data objects. A list can be used for storing data object 7 | * pointers or integer values (except zero). 8 | * 9 | * The application using this library only has to deal with a single list 10 | * pointer and its data objects. It does not have to deal with list nodes 11 | * as is often seen, neither do the the data objects need to supply space 12 | * for list pointers (often called and ). This list type 13 | * is generally called to be 'non-intrusive'. 14 | * The price paid for this convenience is that nodes can not be accessed 15 | * randomly, which means that deleting a node may require a linear search. 16 | * The list does however keeps a pointer to the last accessed list node; 17 | * the supplied set of operations relative to this node still makes this 18 | * kind of list very useful for many applications without (much) perfor- 19 | * mance loss compared to 'more traditional' linked lists in C. 20 | * 21 | * NOTES 22 | * Doing something that is not allowed, or entering a condition that is 23 | * regarded as an error, will result in a 'failed assertion', when this 24 | * module has been built with DEBUG defined. The routine descriptions 25 | * tell what to watch out for. 26 | * 27 | * INTERNAL 28 | * The idea of using a dummy node was taken from "Obfuscated C and Other 29 | * Mysteries" by Don Libes, John Wiley & Sons - 1993, chapter 11. It 30 | * results in simpler list operation code. 31 | * 32 | * down--> <--up 33 | * after--> <--before 34 | * 35 | * +-------------------- - --------------+ 36 | * | | 37 | * +--------------+ V Head Node Tail Node | 38 | * | | +-------+ +-------+ +-------+ | 39 | * | pHead---------->|/pNext------>| pNext---- - -->| pNext----+ 40 | * | | |///////| | | | | 41 | * | pNodeLast--->? +----pPrev/|<------pPrev |<- - -----pPrev | 42 | * | | | |///////| | | | | 43 | * | count | | |/pData/| | pData | +->| pData | 44 | * | | | +-- | --+ +-- | --+ | +-- | --+ 45 | * +--------------+ | | | | | 46 | * | V V | V 47 | * | ### ### | ### 48 | * //// = Dummy Node | ### ### | ### 49 | * | | 50 | * ### = User Data +--------------------------- - + 51 | * 52 | * Notice that pList->pHead->pPrev points to the tail of the list; this 53 | * is used a number of times in the code below. 54 | * 55 | * For efficiency some short code fragments show up a number of times 56 | * in different routines, instead of nesting the routines. 57 | * 58 | * INCLUDE FILES 59 | * List.h 60 | * 61 | * COPYRIGHT 62 | * You are free to use, copy or modify this software at your own risk. 63 | * 64 | * AUTHOR 65 | * Cornelis van der Bent. Please let me know if you have comments or find 66 | * flaws: cg_vanderbent@mail.com. Enjoy! 67 | * 68 | * MODIFICATION HISTORY 69 | * 1999/04/12 vdbent Thorough test and debugging; beta release. 70 | * 1999/03/09 kees Composed. 71 | * 72 | *****************************************************************************/ 73 | 74 | #include 75 | #include "List.h" 76 | #include "Assert.h" /* includes "Except.h" which defines return() macro */ 77 | 78 | 79 | /****************************************************************************** 80 | * 81 | * ListCreate - create empty list 82 | * 83 | * DESCRIPTION 84 | * This routine creates an empty list. 85 | * 86 | * SIDE EFFECTS 87 | * None. 88 | * 89 | * RETURNS 90 | * Pointer to empty list. 91 | */ 92 | 93 | List * ListCreate(void) 94 | { 95 | List * pList; 96 | 97 | pList = malloc(sizeof(List)); 98 | 99 | pList->pHead = malloc(sizeof(ListNode)); 100 | 101 | pList->pHead->pNext = pList->pHead->pPrev = pList->pHead; 102 | 103 | pList->pNodeLast = NULL; 104 | pList->count = 0; 105 | 106 | return pList; 107 | } 108 | 109 | 110 | /****************************************************************************** 111 | * 112 | * ListDestroy - free list but not user data 113 | * 114 | * DESCRIPTION 115 | * This routine frees the list handle and the nodes, but does not free 116 | * the user data. 117 | * 118 | * SIDE EFFECTS 119 | * None. 120 | * 121 | * RETURNS 122 | * N/A. 123 | */ 124 | 125 | void ListDestroy( 126 | List * pList) /* pointer to list */ 127 | { 128 | ListNode * pNode; 129 | 130 | assert(pList != NULL); 131 | 132 | pNode = pList->pHead->pNext; 133 | while (pNode != pList->pHead) 134 | { 135 | ListNode * pNext; 136 | 137 | pNext = pNode->pNext; 138 | free(pNode); 139 | pNode = pNext; 140 | } 141 | 142 | free(pList->pHead); 143 | free(pList); 144 | } 145 | 146 | 147 | /****************************************************************************** 148 | * 149 | * ListDestroyData - free list including user data 150 | * 151 | * DESCRIPTION 152 | * This routine frees the list handle and the nodes, and does also free 153 | * the user data using free(); the caller is responsible that all of this 154 | * user data was allocated with routines compatible with free(). 155 | * 156 | * SIDE EFFECTS 157 | * None. 158 | * 159 | * RETURNS 160 | * N/A. 161 | */ 162 | 163 | void ListDestroyData( 164 | List * pList) /* pointer to list */ 165 | { 166 | ListNode * pNode; 167 | 168 | assert(pList != NULL); 169 | 170 | pNode = pList->pHead->pNext; 171 | while (pNode != pList->pHead) 172 | { 173 | ListNode * pNext; 174 | 175 | pNext = pNode->pNext; 176 | free(pNode->pData); 177 | free(pNode); 178 | pNode = pNext; 179 | } 180 | 181 | free(pList->pHead); 182 | free(pList); 183 | } 184 | 185 | 186 | /****************************************************************************** 187 | * 188 | * ListAddHead - add node to head of list 189 | * 190 | * DESCRIPTION 191 | * This routine adds the specified data object value at the head of the 192 | * specified list. The last accessed list node is set to the added 193 | * node. 194 | * 195 | * SIDE EFFECTS 196 | * None. 197 | * 198 | * RETURNS 199 | * N/A. 200 | */ 201 | 202 | void ListAddHead( 203 | List * pList, /* pointer to list */ 204 | void * pData) /* data object value */ 205 | { 206 | ListNode * pNode; 207 | 208 | assert(pList != NULL); 209 | 210 | pNode = malloc(sizeof(ListNode)); 211 | 212 | pNode->pData = pData; 213 | (pNode->pNext = pList->pHead->pNext)->pPrev = pNode; 214 | (pList->pHead->pNext = pNode)->pPrev = pList->pHead; 215 | 216 | pList->pNodeLast = pNode; 217 | pList->count++; 218 | } 219 | 220 | 221 | /****************************************************************************** 222 | * 223 | * ListAddTail - add node to tail of list 224 | * 225 | * DESCRIPTION 226 | * This routine adds the specified data object value at the tail of the 227 | * specified list. The last accessed list node is set to the added 228 | * node. 229 | * 230 | * SIDE EFFECTS 231 | * None. 232 | * 233 | * RETURNS 234 | * N/A. 235 | */ 236 | 237 | void ListAddTail( 238 | List * pList, /* pointer to list */ 239 | void * pData) /* data object value */ 240 | { 241 | ListNode * pNode; 242 | 243 | assert(pList != NULL); 244 | 245 | pNode = malloc(sizeof(ListNode)); 246 | 247 | pNode->pData = pData; 248 | (pNode->pPrev = pList->pHead->pPrev)->pNext = pNode; 249 | (pList->pHead->pPrev = pNode)->pNext = pList->pHead; 250 | 251 | pList->pNodeLast = pNode; 252 | pList->count++; 253 | } 254 | 255 | 256 | /****************************************************************************** 257 | * 258 | * ListAddBefore - add node before last accessed node 259 | * 260 | * DESCRIPTION 261 | * This routine adds the specified data object value in the specified 262 | * list just before the node that was last accessed by one of the 263 | * routines from this library that set it. 'Before' means towards the 264 | * head of the list. The last accessed list node is set to the added 265 | * node. 266 | * 267 | * Nothing happens when the last accessed list node is not set; this 268 | * causes a failed assertion when DEBUG defined. 269 | * 270 | * SIDE EFFECTS 271 | * None. 272 | * 273 | * RETURNS 274 | * N/A. 275 | */ 276 | 277 | void ListAddBefore( 278 | List * pList, /* pointer to list */ 279 | void * pData) /* data object value */ 280 | { 281 | ListNode * pNode; 282 | 283 | assert(pList != NULL); 284 | validate(pList->pNodeLast != NULL, NOTHING); 285 | 286 | pNode = malloc(sizeof(ListNode)); 287 | 288 | pNode->pData = pData; 289 | (pNode->pPrev = pList->pNodeLast->pPrev)->pNext = pNode; 290 | (pList->pNodeLast->pPrev = pNode)->pNext = pList->pNodeLast; 291 | 292 | pList->pNodeLast = pNode; 293 | pList->count++; 294 | } 295 | 296 | 297 | /****************************************************************************** 298 | * 299 | * ListAddAfter - add node after last accessed node 300 | * 301 | * DESCRIPTION 302 | * This routine adds the specified data object value in the specified 303 | * list right after the node that was last accessed by one of the 304 | * routines from this library that set it. 'After' means towards the 305 | * tail of the list. The last accessed list node is set to the added 306 | * node. 307 | * 308 | * Nothing happens when the last accessed list node is not set; this 309 | * causes a failed assertion when DEBUG defined. 310 | * 311 | * SIDE EFFECTS 312 | * None. 313 | * 314 | * RETURNS 315 | * N/A. 316 | */ 317 | 318 | void ListAddAfter( 319 | List * pList, /* pointer to list */ 320 | void * pData) /* data object value */ 321 | { 322 | ListNode * pNode; 323 | 324 | assert(pList != NULL); 325 | validate(pList->pNodeLast != NULL, NOTHING); 326 | 327 | pNode = malloc(sizeof(ListNode)); 328 | 329 | pNode->pData = pData; 330 | (pNode->pNext = pList->pNodeLast->pNext)->pPrev = pNode; 331 | (pList->pNodeLast->pNext = pNode)->pPrev = pList->pNodeLast; 332 | 333 | pList->pNodeLast = pNode; 334 | pList->count++; 335 | } 336 | 337 | 338 | /****************************************************************************** 339 | * 340 | * ListRemoveHead - remove head node from list 341 | * 342 | * DESCRIPTION 343 | * This routine removes the head list node from the specified list. The 344 | * last accessed list node is reset. 345 | * 346 | * It is not allowed to pass this routine an empty list. 347 | * 348 | * SIDE EFFECTS 349 | * None. 350 | * 351 | * RETURNS 352 | * Removed data object value. 353 | */ 354 | 355 | void * ListRemoveHead( 356 | List * pList) /* pointer to list */ 357 | { 358 | void * pData; 359 | ListNode * pNode; 360 | 361 | assert(pList != NULL); 362 | validate(pList->count > 0, NULL); 363 | 364 | pNode = pList->pHead->pNext; 365 | 366 | pData = pNode->pData; 367 | (pList->pHead->pNext = pNode->pNext)->pPrev = pList->pHead; 368 | free(pNode); 369 | 370 | pList->pNodeLast = NULL; 371 | pList->count--; 372 | 373 | return pData; 374 | } 375 | 376 | 377 | /****************************************************************************** 378 | * 379 | * ListRemoveTail - remove tail node from list 380 | * 381 | * DESCRIPTION 382 | * This routine removes the tail list node from the specified list. The 383 | * last accessed list node is reset. 384 | * 385 | * It is not allowed to pass this routine an empty list. 386 | * 387 | * SIDE EFFECTS 388 | * None. 389 | * 390 | * RETURNS 391 | * Removed data object value. 392 | */ 393 | 394 | void * ListRemoveTail( 395 | List * pList) /* pointer to list */ 396 | { 397 | void * pData; 398 | ListNode * pNode; 399 | 400 | assert(pList != NULL); 401 | validate(pList->count > 0, NULL); 402 | 403 | pNode = pList->pHead->pPrev; 404 | 405 | pData = pNode->pData; 406 | (pList->pHead->pPrev = pNode->pPrev)->pNext = pList->pHead; 407 | free(pNode); 408 | 409 | pList->pNodeLast = NULL; 410 | pList->count--; 411 | 412 | return pData; 413 | } 414 | 415 | 416 | /****************************************************************************** 417 | * 418 | * ListRemove - remove specified node from list 419 | * 420 | * DESCRIPTION 421 | * This routine removes the node with the specified data object value 422 | * The last accessed list node is reset. 423 | * 424 | * It is an error if the specified node is not in the list. It is not 425 | * allowed to pass this routine an empty list. 426 | * 427 | * SIDE EFFECTS 428 | * None. 429 | * 430 | * RETURNS 431 | * Removed data object value. 432 | */ 433 | 434 | void * ListRemove( 435 | List * pList, /* pointer to list */ 436 | void * pData) /* data object value */ 437 | { 438 | ListNode * pNode; 439 | 440 | assert(pList != NULL); 441 | validate(pList->count > 0, NULL); 442 | 443 | pNode = pList->pHead->pNext; 444 | while (pNode != pList->pHead && pNode->pData != pData) 445 | pNode = pNode->pNext; 446 | validate(pNode->pData == pData, NULL); 447 | 448 | pNode->pNext->pPrev = pNode->pPrev; 449 | pNode->pPrev->pNext = pNode->pNext; 450 | free(pNode); 451 | 452 | pList->pNodeLast = NULL; 453 | pList->count--; 454 | 455 | return pData; 456 | } 457 | 458 | 459 | /****************************************************************************** 460 | * 461 | * ListRemoveLast - remove last accessed node from list 462 | * 463 | * DESCRIPTION 464 | * This routine removes the node which was last accessed by one of the 465 | * routines in this library that set it. Subsequently the last accessed 466 | * list node is set to the next node for convenience. 467 | * 468 | * It is an error if the last accessed node was not set by one of the 469 | * routines in this library. It is not allowed to pass this routine an 470 | * empty list. 471 | * 472 | * SIDE EFFECTS 473 | * None. 474 | * 475 | * RETURNS 476 | * Removed data object value. 477 | */ 478 | 479 | void * ListRemoveLast( 480 | List * pList) /* pointer to list */ 481 | { 482 | void * pData; 483 | ListNode * pNext; 484 | 485 | assert(pList != NULL); 486 | validate(pList->pNodeLast != NULL, NULL); 487 | 488 | pData = pList->pNodeLast->pData; 489 | 490 | pNext = pList->pNodeLast->pNext; 491 | 492 | pList->pNodeLast->pNext->pPrev = pList->pNodeLast->pPrev; 493 | pList->pNodeLast->pPrev->pNext = pList->pNodeLast->pNext; 494 | free(pList->pNodeLast); 495 | 496 | pList->pNodeLast = pNext; 497 | pList->count--; 498 | 499 | return pData; 500 | } 501 | 502 | 503 | /****************************************************************************** 504 | * 505 | * ListHead - get head data object value 506 | * 507 | * DESCRIPTION 508 | * This routine returns the user data object value of the head node of 509 | * the specified list. The last accessed list node is reset if the list 510 | * is empty, otherwise it is set to the list head. 511 | * 512 | * SIDE EFFECTS 513 | * None. 514 | * 515 | * RETURNS 516 | * Head data object value, or NULL if empty. 517 | */ 518 | 519 | void * ListHead( 520 | List * pList) /* pointer to list */ 521 | { 522 | assert(pList != NULL); 523 | 524 | if (pList->count == 0) 525 | return NULL; 526 | else 527 | return 0, (pList->pNodeLast = pList->pHead->pNext)->pData; 528 | } 529 | 530 | 531 | /****************************************************************************** 532 | * 533 | * ListTail - get tail data object value 534 | * 535 | * DESCRIPTION 536 | * This routine returns the user data object value of the tail node of 537 | * the specified list. The last accessed list node is reset if the list 538 | * is empty, otherwise it is set to the list tail. 539 | * 540 | * SIDE EFFECTS 541 | * None. 542 | * 543 | * RETURNS 544 | * Head data object value, or NULL if empty. 545 | */ 546 | 547 | void * ListTail( 548 | List * pList) /* pointer to list */ 549 | { 550 | assert(pList != NULL); 551 | 552 | if (pList->count == 0) 553 | return NULL; 554 | else 555 | return 0, (pList->pNodeLast = pList->pHead->pPrev)->pData; 556 | } 557 | 558 | 559 | /****************************************************************************** 560 | * 561 | * ListLast - get last accessed data object value 562 | * 563 | * DESCRIPTION 564 | * This routine returns the user data object value of the last accessed 565 | * node of the specified list. The last accessed list node is not 566 | * affected. 567 | * 568 | * When the last accessed list node is not set, which is also the case 569 | * when the list is empty, NULL is returned. 570 | * 571 | * SIDE EFFECTS 572 | * None. 573 | * 574 | * RETURNS 575 | * Last accessed data object value, or NULL if not set. 576 | */ 577 | 578 | void * ListLast( 579 | List * pList) /* pointer to list */ 580 | { 581 | assert(pList != NULL); 582 | 583 | if (pList->pNodeLast == NULL) 584 | return NULL; 585 | else 586 | return pList->pNodeLast->pData; 587 | } 588 | 589 | 590 | /****************************************************************************** 591 | * 592 | * ListNext - get next data object value 593 | * 594 | * DESCRIPTION 595 | * This routine returns the user data object value of the next node 596 | * with respect to the last accessed list node. The last accessed list 597 | * node is set to the next node, or is reset if the tail is passed. 598 | * 599 | * It is an error if the last accessed list node is not set; which is 600 | * also the case when the list is empty. 601 | * 602 | * SIDE EFFECTS 603 | * None. 604 | * 605 | * RETURNS 606 | * Next data object value, or NULL if already at tail. 607 | */ 608 | 609 | void * ListNext( 610 | List * pList) /* pointer to list */ 611 | { 612 | assert(pList != NULL); 613 | validate(pList->pNodeLast != NULL, NULL); 614 | 615 | if ((pList->pNodeLast = pList->pNodeLast->pNext) == pList->pHead) 616 | { 617 | pList->pNodeLast = NULL; 618 | return NULL; 619 | } 620 | else 621 | return pList->pNodeLast->pData; 622 | } 623 | 624 | 625 | /****************************************************************************** 626 | * 627 | * ListPrev - get previous data object value 628 | * 629 | * DESCRIPTION 630 | * This routine returns the user data object value of the previous node 631 | * with respect to the last accessed list node. The last accessed list 632 | * node is set to the previous node, or is reset if the head is passed. 633 | * 634 | * It is an error if the last accessed list node is not set; which is 635 | * also the case when the list is empty. 636 | * 637 | * SIDE EFFECTS 638 | * None. 639 | * 640 | * RETURNS 641 | * Next data object value, or NULL if already at head. 642 | */ 643 | 644 | void * ListPrev( 645 | List * pList) /* pointer to list */ 646 | { 647 | assert(pList != NULL); 648 | validate(pList->pNodeLast != NULL, NULL); 649 | 650 | if ((pList->pNodeLast = pList->pNodeLast->pPrev) == pList->pHead) 651 | { 652 | pList->pNodeLast = NULL; 653 | return NULL; 654 | } 655 | else 656 | return pList->pNodeLast->pData; 657 | } 658 | 659 | 660 | /****************************************************************************** 661 | * 662 | * ListCount - report number of nodes in list 663 | * 664 | * DESCRIPTION 665 | * This routine returns the number of nodes in the specified list. 666 | * 667 | * SIDE EFFECTS 668 | * None. 669 | * 670 | * RETURNS 671 | * Number of nodes in list. 672 | */ 673 | 674 | int ListCount( 675 | List * pList) /* pointer to list */ 676 | { 677 | assert(pList != NULL); 678 | 679 | return pList->count; 680 | } 681 | 682 | 683 | /****************************************************************************** 684 | * 685 | * ListFind - find list node 686 | * 687 | * DESCRIPTION 688 | * This routine finds the node with the specified data object value. If 689 | * nothing was found the last accessed list node is not affected, 690 | * otherwise it is set to the found node. 691 | * 692 | * SIDE EFFECTS 693 | * None. 694 | * 695 | * RETURNS 696 | * Found data object value, or NULL if not found or empty list. 697 | */ 698 | 699 | void * ListFind( 700 | List * pList, /* pointer to list */ 701 | void * pData) /* data object value */ 702 | { 703 | ListNode * pNode; 704 | 705 | assert(pList != NULL); 706 | 707 | pNode = pList->pHead->pNext; 708 | while (pNode != pList->pHead && pNode->pData != pData) 709 | pNode = pNode->pNext; 710 | 711 | if (pNode->pData == pData) 712 | { 713 | pList->pNodeLast = pNode; 714 | return pData; 715 | } 716 | else 717 | return NULL; 718 | } 719 | 720 | 721 | /****************************************************************************** 722 | * 723 | * ListSplitBefore - split list just before last node 724 | * 725 | * DESCRIPTION 726 | * This routine splits up the specified list in two parts. The split is 727 | * made just before the last accessed list node. A new list is created 728 | * for the part before the last accessed node. The last accessed list 729 | * node for the original list is not affected. And the last accessed 730 | * list node for the new list is reset. 731 | * 732 | * It is not allowed to pass this routine an empty list. If the list 733 | * contains only one node, the new list will be empty. 734 | * 735 | * SIDE EFFECTS 736 | * None. 737 | * 738 | * RETURNS 739 | * Pointer to new list containing nodes before original last accessed 740 | * list node. 741 | */ 742 | 743 | List * ListSplitBefore( 744 | List * pListOrg) /* pointer to original list */ 745 | { 746 | List * pListNew; 747 | ListNode * pNodeOrg; 748 | 749 | assert(pListOrg != NULL); 750 | validate(pListOrg->count > 0, NULL); 751 | validate(pListOrg->pNodeLast != NULL, NULL); 752 | 753 | pListNew = malloc(sizeof(List)); 754 | 755 | pListNew->pHead = malloc(sizeof(ListNode)); 756 | pListNew->pHead->pData = NULL; 757 | pListNew->count = 0; 758 | 759 | pNodeOrg = pListOrg->pHead->pNext; 760 | while (pNodeOrg != pListOrg->pNodeLast) 761 | { 762 | pListOrg->count--; 763 | pListNew->count++; 764 | 765 | pNodeOrg = pNodeOrg->pNext; 766 | } 767 | 768 | if (pListNew->count == 0) 769 | { 770 | pListNew->pHead->pPrev = pListNew->pHead->pNext = pListNew->pHead; 771 | } 772 | else 773 | { 774 | /* connect list part to new list */ 775 | pListNew->pHead->pNext = pListOrg->pHead->pNext; 776 | pListNew->pHead->pNext->pPrev = pListNew->pHead; 777 | pListNew->pHead->pPrev = pListOrg->pNodeLast->pPrev; 778 | pListNew->pHead->pPrev->pNext = pListNew->pHead; 779 | 780 | /* bind last accessed node and original dummy node together */ 781 | pListOrg->pHead->pNext = pListOrg->pNodeLast; 782 | pListOrg->pNodeLast->pPrev = pListOrg->pHead; 783 | } 784 | 785 | pListNew->pNodeLast = NULL; 786 | 787 | return pListNew; 788 | } 789 | 790 | 791 | /****************************************************************************** 792 | * 793 | * ListSplitAfter - split list just after last node 794 | * 795 | * DESCRIPTION 796 | * This routine splits up the specified list in two parts. The split is 797 | * made just after the last accessed list node. A new list is created 798 | * for the part after the last accessed node. The last accessed list 799 | * node for the original list is not affected. And the last accessed 800 | * list node for the new list is reset. 801 | * 802 | * It is not allowed to pass this routine an empty list. If the list 803 | * contains only one node, the new list will be empty. 804 | * 805 | * SIDE EFFECTS 806 | * None. 807 | * 808 | * RETURNS 809 | * Pointer to new list containing nodes after original last accessed 810 | * list node. 811 | */ 812 | 813 | List * ListSplitAfter( 814 | List * pListOrg) /* pointer to original list */ 815 | { 816 | List * pListNew; 817 | ListNode * pNodeOrg; 818 | 819 | assert(pListOrg != NULL); 820 | validate(pListOrg->count > 0, NULL); 821 | validate(pListOrg->pNodeLast != NULL, NULL); 822 | 823 | pListNew = malloc(sizeof(List)); 824 | 825 | pListNew->pHead = malloc(sizeof(ListNode)); 826 | pListNew->pHead->pData = NULL; 827 | pListNew->count = 0; 828 | 829 | pNodeOrg = pListOrg->pNodeLast->pNext; 830 | while (pNodeOrg != pListOrg->pHead) 831 | { 832 | pListOrg->count--; 833 | pListNew->count++; 834 | 835 | pNodeOrg = pNodeOrg->pNext; 836 | } 837 | 838 | if (pListNew->count == 0) 839 | { 840 | pListNew->pHead->pPrev = pListNew->pHead->pNext = pListNew->pHead; 841 | } 842 | else 843 | { 844 | /* connect list part to new list */ 845 | pListNew->pHead->pNext = pListOrg->pNodeLast->pNext; 846 | pListNew->pHead->pNext->pPrev = pListNew->pHead; 847 | pListNew->pHead->pPrev = pListOrg->pHead->pPrev; 848 | pListNew->pHead->pPrev->pNext = pListNew->pHead; 849 | 850 | /* bind last accessed node and original dummy node together */ 851 | pListOrg->pNodeLast->pNext = pListOrg->pHead; 852 | pListOrg->pHead->pPrev = pListOrg->pNodeLast; 853 | } 854 | 855 | pListNew->pNodeLast = NULL; 856 | 857 | return pListNew; 858 | } 859 | 860 | 861 | /****************************************************************************** 862 | * 863 | * ListConcat - concatenate two lists 864 | * 865 | * DESCRIPTION 866 | * This routine concatenates the second list to the tail of 867 | * the first list . Either list (or both) may be empty. After 868 | * the operation the handle is destroyed. The last accessed 869 | * list node of the resulting list is reset. 870 | * 871 | * SIDE EFFECTS 872 | * None. 873 | * 874 | * RETURNS 875 | * List pointer containing the nodes of both lists. 876 | */ 877 | 878 | List * ListConcat( 879 | List * pListDst, /* pointer to destination list */ 880 | List * pListAdd) /* pointer to list to be added at tail */ 881 | { 882 | assert(pListDst != NULL); 883 | assert(pListAdd != NULL); 884 | 885 | switch (((pListAdd->count > 0) << 1) | (pListDst->count > 0)) 886 | { 887 | case 0: 888 | /* both lists empty */ 889 | 890 | break; 891 | 892 | case 1: 893 | /* destination list not empty and add list empty */ 894 | 895 | break; 896 | 897 | case 2: 898 | /* destination list empty and add list not empty */ 899 | 900 | pListDst->pHead->pNext = pListAdd->pHead->pNext; 901 | pListDst->pHead->pNext->pPrev = pListDst->pHead; 902 | pListDst->pHead->pPrev = pListAdd->pHead->pPrev; 903 | pListDst->pHead->pPrev->pNext = pListDst->pHead; 904 | break; 905 | 906 | case 3: 907 | /* both lists not empty */ 908 | 909 | pListAdd->pHead->pPrev->pNext = pListDst->pHead; 910 | pListDst->pHead->pPrev->pNext = pListAdd->pHead->pNext; 911 | pListAdd->pHead->pNext->pPrev = pListDst->pHead->pPrev; 912 | pListDst->pHead->pPrev = pListAdd->pHead->pPrev; 913 | break; 914 | } 915 | 916 | pListDst->pNodeLast = NULL; 917 | pListDst->count += pListAdd->count; 918 | 919 | free(pListAdd->pHead); 920 | free(pListAdd); 921 | 922 | return pListDst; 923 | } 924 | 925 | 926 | /* end of List.c */ 927 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Let's Finally Try to Catch C -- Java like exception handling in C 2 | ================================================================= 3 | 4 | 5 | Preface 6 | ------- 7 | Some time ago I was working on large deeply nested C code (a recursive-descent 8 | parser) and wanted to be able to jump away (to outside the parser) on a fatal 9 | condition. Instead of using the setjmp()/longjmp()-pair as is for this occa- 10 | sion, I thought it would be nice to have a generic/reusable mechanism like in 11 | C++ or Java. 12 | 13 | I started with simple 'try', 'catch' and 'throw' macros that worked fine for 14 | this application, but did not allow nesting and lacked other useful features. 15 | When I realized that a complete/generic exception handling package might be 16 | useful for others and myself, I took up the challenge to create such a package 17 | that looks like Java in the best possible way. 18 | 19 | It was really exciting and fun to in a way extend the C language and I am 20 | happy to share it with you! I hope you will enjoy this package, and if you 21 | have comments or find flaws please let me know: 22 | 23 | Cornelis van der Bent 24 | cornelis@meaning-matters.com 25 | 26 | April 1999. 27 | 28 | 29 | Article Introduction 30 | -------------------- 31 | Well written C code has to deal with four kinds of anomalies: regular failures 32 | (e.g., unable to open file), out-of-memory conditions, run-time error signals 33 | (like SIGSEGV), and failed assertions. Traditionally, each is handled in a 34 | different way: returning error codes, exiting, using setjmp()/longjmp(), and 35 | printing a message followed by abort() respectively. Furthermore, C does not 36 | support centralized exception handling like C++ and Java do. This all results 37 | in functional code getting cluttered with exception handling statements. 38 | 39 | While working on a recursive-descent parser in C, I needed a mechanism to skip 40 | any number of stack frames for easy error handling. The simple try, catch and 41 | throw macro's I created using setjmp()/longjmp() worked fine, but did not allow 42 | nesting and only addressed one kind of errors. Soon after, I took up the 43 | challenge to create a complete centralized exception handling package in plain 44 | ANSI C, that looks like Java (and C++) in the best possible way, is easy to use 45 | and has as little side-effects as possible. 46 | 47 | In this article I will show you how to use the package, describing every 48 | feature in detail; see Example 1 and Figure 1 to get a taste. Also, I will 49 | explain some interesting aspects of the implementation. Because the functio- 50 | nality is so very similar to what C++ and Java offer, reading this article 51 | will be of help even if you will never use my package. Generally speaking, 52 | exception handling is an important and yet difficult subject, thinking about 53 | it and applying the right techniques saves time and makes life easier. 54 | 55 | Figure 1: 56 | * Catching all four kinds of exceptions: explicitly thrown by application, 57 | out-of-memory errors (using a simple optional module not being discussed 58 | here), failed assertions, and signals (SIGABRT, SIGFPE, SIGILL and SIGSEGV). 59 | * Unlimited user extendable exception class hierarchy. Classes can extend 60 | classes defined in other (compiled) modules. An exception class can be 61 | caught with any (in)direct ancestor or itself. Just like Java and C++. 62 | * Can be used in any multi-threading environment. Compile-time configuration. 63 | Only requires two platform specific functions: one getting the thread ID and 64 | the other for mutual exclusion locking. 65 | * Unlimited nesting in try, catch and finally blocks. 66 | * Run-time check for duplicate and superfluous catch clauses (DEBUG option). 67 | A similar check as Java and C++ do at compile-time. 68 | * Exception propagation, overruling and rethrowing identical to Java. 69 | * Always executed finally block, also when using return(), break or continue. 70 | * Looks very similar to Java (or C++). C implementation details are nicely 71 | hidden. Little side effects. 72 | * Caught exception member functions like getMessage(). No context pointer 73 | as first argument; gives a real OO look. 74 | * All messages (except those of signals) contain file name and line number. 75 | * Default behavior or a message printed, for not caught exceptions. 76 | * Fully ANSI C compliant code and no platform dependencies. 77 | * Well documented: source code, extensive README and this article. (Due to 78 | limited space the comments were left out from the listings.) 79 | * Free. 80 | 81 | 82 | 83 | Getting Started 84 | --------------- 85 | For a single-threaded application you only need to include "Except.h" and link 86 | the supplied modules. 87 | 88 | Example 1: When the critical code throws a MyFault exception or any of its 89 | subclasses, it is handled. For any other exception a message containing 90 | its name and information where thrown (file & line) are printed, followed by 91 | a rethrow. The clean up code is always executed. 92 | 93 | #include 94 | 95 | ex_class_define(MyFault, Exception); 96 | ex_class_define(AlsoMyFault, MyFault); 97 | ex_class_define(AgainMyFault, MyFault); 98 | 99 | void TryMe(void) { 100 | try { 101 | /* critical code */ 102 | } 103 | catch (MyFault, e) { 104 | /* handle one of my faults */ 105 | } 106 | catch (Throwable, e) { 107 | printf("%s\n", e->getMessage()); 108 | throw (e, NULL); 109 | } 110 | finally { 111 | /* clean up code */ 112 | } 113 | } 114 | 115 | 116 | Catching 117 | -------- 118 | At this moment the exception supplies four public member functions that may 119 | be called inside the 'catch' block: 120 | 121 | char *getMessage(void) - returns a pointer to a static string containing a 122 | detailed description of the exception 123 | int getClass(void) - returns the exception class (address) 124 | void *getData(void) - returns the pointer to (application) data asso- 125 | ciated with the exception: the second argument 126 | of throw(), for EX_ASSERT it is a pointer to the 127 | static string containing the false expression, 128 | for the other exceptions it is NULL 129 | 130 | The convenience of not having to supply these functions a pointer to the 131 | current exception, comes with a price. In a multi-threaded application, 132 | a fast hashtable lookup is performed at 133 | each call; because this is done as part of handling an error condition, it is 134 | not a big deal (for most applications). In a single-threaded application only 135 | a very fast direct lookup (involving a few if-statements and some pointer 136 | arithmetic) is performed. 137 | 138 | It is important to know that the pointer to the caught exception ( in most 139 | examples), is only valid inside its 'catch' block. You may pass it to a 140 | routine (as type) as long as you stay outside another 'try' 141 | statement. Also note that getMessage() returns a pointer to a static string 142 | that will be overwritten or freed. Finally, it is the responsibility of the 143 | application to free allocated memory passed with throw() and returned by getData(). 144 | <<>> 148 | 149 | Of course more than one catch() can be specified for a critical code section. 150 | As in Java, the evaluation process is performed from top to bottom and an 151 | exception is caught by only one (the first matching) 'catch' clause. It is 152 | possible to create superfluous or duplicate 'catch' clauses: 153 | 154 | try {} 155 | catch (Exception, e) {} 156 | catch (FailedAssertion, e) {} /* superfluous: already caught by Exception*/ 157 | finally {} 158 | 159 | or: 160 | 161 | try {} 162 | catch (FailedAssertion, e) {} 163 | catch (RuntimeException, e) {} 164 | catch (FailedAssertion, e) {} /* duplicate */ 165 | finally {} 166 | 167 | When DEBUG is #defined when compiling the application module, a run-time check 168 | for all possible conditions like this, is done as part of executing the 'try' 169 | statement. So this check is performed even before the 'try' block is executed 170 | and consequently also before an exception has occurred! Java informs you about 171 | conditions like this at compile-time, but this is not possible in C; at least 172 | you are informed and it is done as early as possible. 173 | 174 | These checks add a little overhead; fortunately the persistent overhead is zero 175 | when DEBUG is not #defined. 176 | 177 | It is allowed to have no 'catch' clauses at all. But in that case you will 178 | get a warning that 'catch' clause(s) are missing, when DEBUG is #defined. 179 | 180 | 181 | As some C flow control statements, catch() expects to be followed by a 182 | statement. So this can be a single statement closed with a semicolon or a 183 | compound statement between braces. Consequently it is also allowed to write: 184 | 185 | catch (Throwable, e); /* 'catch' followed by empty statement */ 186 | 187 | which will only catch any exception and performs no further actions. 188 | This will of course not affect the run-time checking discussed above. 189 | 190 | There is a predefined exception class hierarchy which you can extends as 191 | far as you like, there are no limits! 192 | 193 | /* this is in Except.h */ 194 | ex_class_declare(Exception, Throwable); 195 | ex_class_declare(OutOfMemoryError, Exception); 196 | ex_class_declare(FailedAssertion, Exception); 197 | ex_class_declare(RuntimeException, Exception); 198 | ex_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */ 199 | ex_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */ 200 | ex_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */ 201 | ex_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */ 202 | 203 | /* this is in Except.c */ 204 | ex_class_define(Exception, Throwable); 205 | ex_class_define(OutOfMemoryError, Exception); 206 | ex_class_define(FailedAssertion, Exception); 207 | ex_class_define(RuntimeException, Exception); 208 | ex_class_define(AbnormalTermination, RuntimeException); /* SIGABRT */ 209 | ex_class_define(ArithmeticException, RuntimeException); /* SIGFPE */ 210 | ex_class_define(IllegalInstruction, RuntimeException); /* SIGILL */ 211 | ex_class_define(SegmentationFault, RuntimeException); /* SIGSEGV */ 212 | 213 | It's simple, don't you think. 214 | 215 | Erik: zo zien de [check] messages er uit: 216 | 217 | Level2Exception: file "Test.c", line 379. 218 | 219 | Superfluous catch(Level1Exception): file "Test.c", line 370; already caught by Exception at line 366. 220 | 221 | Duplicate catch(Level1Exception): file "Test.c", line 419; already caught at line 418. 222 | 223 | Warning: No catch clause(s): file "Test.c", line 396. 224 | 225 | 226 | 227 | Throwing 228 | -------- 229 | Deliberately throwing an exception is quite simple using throw(). The first 230 | argument is the exception class. With the 231 | second argument you can pass a void pointer value which can be retrieved in 232 | the 'catch' code with the exceptions' getData() member function (as described 233 | in "Catching"). Do not pass a pointer to a non-static local variable (which 234 | resides on the stack), because it gets out of scope (the stack is unwound) if 235 | the exception is caught in another routine. This is regular C programming 236 | practice. 237 | 238 | Exceptions can be thrown from any scope: 239 | 240 | outside - it will be reported as being lost 241 | inside 'try' block - it will be caught by corresponding 'catch' or will 242 | otherwise be propagated (more on this later) 243 | inside 'catch' block - it will be propagated (...) 244 | inside 'finally' block - it will override any pending exception and 245 | will be propagated itself (...) 246 | 247 | Note that there is no difference if the 'throw' is done directly in the 248 | exception handling block, or if there are any number of routine calls in 249 | between: 250 | 251 | There(void) 252 | { 253 | throw (MyException, "How are you?"); 254 | } 255 | 256 | Hi(void) 257 | { 258 | There(); 259 | } 260 | 261 | What(void) 262 | { 263 | try 264 | { 265 | Hi(); 266 | } 267 | catch (Exception, e) 268 | { 269 | printf("%s\n", e->getData()); 270 | } 271 | finally; 272 | } 273 | 274 | Invoking What() will result in printing "I'm fine, thank you!" (just kidding). 275 | 276 | 277 | 278 | Rethrowing 279 | ---------- 280 | Inside a 'catch' block you can throw the just caught exception again (to let 281 | it propagate to a higher exception handling level) using rethrow(): 282 | 283 | catch (Throwable, e) 284 | { 285 | throw (e, 0); 286 | } 287 | 288 | Note that the second argument (where you normally place your data pointer) 289 | is not looked at, so the data of the original exception is passed on. 290 | 291 | 292 | Returning 293 | --------- 294 | As in Java the 'finally' block is always executed even when return() is used: 295 | 296 | try 297 | { 298 | throw(MyFault, NULL); 299 | } 300 | catch (MyFault, e) 301 | { 302 | return(-1); 303 | } 304 | finally 305 | { 306 | printf("Just before return.\n"); 307 | } 308 | 309 | This code fragment would indeed print "Just before return.". When "Except.h" 310 | is included return() is defined as macro that takes care of everything and 311 | also works as the regular return() when not directly inside nor outside an 312 | exception handling block. 313 | 314 | 315 | When you use return() inside the 'finally' block, this will override a 316 | pending exception that would otherwise have been propagated. This pending 317 | exception will be lost and no notification message will be printed. Java 318 | behaves in exactly the same manner. 319 | 320 | 321 | Unless you have already redefined it, having return() as a macro is not a 322 | problem in most cases. The macro does however give some overhead, so you 323 | can best avoid it in code sections that frequently use return() and/or have 324 | a high efficiency requirement. Since return() is defined as a macro having 325 | one parameter (always between parentheses), the C preprocessor will do nothing 326 | when you omit the parentheses, yielding the native C 'return': 327 | 328 | return -1; /* uses the native C 'return' */ 329 | 330 | However, when you return an expression that starts with a parenthesis the C 331 | preprocessor will replace it up to the matching closing parenthesis; this will 332 | often/always result in erroneous code: 333 | 334 | return (int *)pData; /* erroneous when return() macro defined */ 335 | 336 | Here the C preprocessor will use "int *" as argument of the return() macro. 337 | There is a simple remedy, by using the C comma operator you can prevent the 338 | expression from starting with a parenthesis: 339 | 340 | return 0,(int *)pData; /* problem solved - no additional overhead */ 341 | 342 | You could also use an intermediate variable (which will probably be optimized 343 | away by modern compilers): 344 | 345 | int *pTmp = (int *)pData; 346 | return pTmp; 347 | 348 | Although this may seem tricky, keep in mind that you may not encounter this 349 | situation often. 350 | 351 | 352 | 353 | Finally 354 | ------- 355 | The 'finally' clause is dealt with in other sections. The only thing to note 356 | is that every 'try' statement must have a (possibly empty) 'finally' clause. 357 | 358 | 359 | 360 | Using 'break' and 'continue' 361 | ---------------------------- 362 | The C 'break' and 'continue' statements used directly inside 'try', 'catch' or 363 | 'finally' (so without enclosing 'while', 'for' or 'swtich' statement) blocks 364 | work differently compared to Java: 365 | 366 | 'break in try' - start executing 'finally' block 367 | 'continue in try' - start executing 'finally' block 368 | 'break in catch' - start executing 'finally' block 369 | 'continue in catch' - start executing 'finally' block 370 | 'break in finally' - leave 'finally' block 371 | 'continue in finally' - leave 'finally' block 372 | 373 | When you embed a 'try' statement in for example a 'while' loop in Java, you can 374 | use 'break' to leave the loop (after first executing the 'finally' code) or use 375 | 'continue' to skip to the next iteration (in this case the 'finally' code is 376 | also executed first). But, if you may need this functionality you can use the 377 | following workaround: 378 | 379 | int breaked; 380 | 381 | breaked = 0; 382 | while (...) 383 | { 384 | try 385 | { 386 | if (...) 387 | breaked = 1; 388 | } 389 | catch (...) {} 390 | finally (...) {} 391 | 392 | if (breaked) 393 | break; 394 | } 395 | 396 | Although 'break' and 'continue' work differently, they can still be used for 397 | what they do do. 398 | 399 | 400 | 401 | Nesting 402 | ------- 403 | You can start a critical code section wherever you like. Nesting can be 404 | done up to unlimited depth. In this way different exception handling levels 405 | can be created (across routine boundaries). 406 | 407 | try 408 | { 409 | try { 410 | try {} 411 | catch (...) {} 412 | finally {} 413 | } 414 | catch(...) {} 415 | finally {} 416 | } 417 | catch 418 | { 419 | try {} 420 | catch(...) {} 421 | finally {} 422 | } 423 | finally 424 | { 425 | try {} 426 | catch(...) {} 427 | finally {} 428 | } 429 | 430 | Not caught exceptions will propagate upwards as expected (see below). 431 | 432 | 433 | 434 | Propagation 435 | ----------- 436 | When an exception is not caught at the exception handling level it occurred, 437 | it is propagated upwards. Propagation can be best understood by thinking that 438 | the exception is 'rethrown' just below its 'finally' block (that belongs to the 439 | level the exception occurred). Then the same rules apply as described above 440 | in "Throwing". The result depends on where this 'rethrow' takes place: 441 | 442 | outside - it will be reported as being lost 443 | inside 'try' block - it will be caught by corresponding 'catch' on this 444 | higher level or will otherwise be propagated again 445 | inside 'catch' block - it will be propagated to yet the next level 446 | inside 'finally' block - it will override any pending exception and 447 | will be propagated to the next level upwards 448 | 449 | Here's an example of this last rule: 450 | 451 | try 452 | { 453 | throw(-777, NULL); /* not caught on this level - pending */ 454 | } 455 | catch (EX_MEMORY, e) {} 456 | catch (-666, e) {} 457 | finally 458 | { 459 | try 460 | { 461 | throw(-321, NULL); /* not caught on this level - 'rethrown' */ 462 | } 463 | catch (-123, e) {} 464 | finally {} 465 | /* 'rethrow(-321)' will override 'throw(-777)' */ 466 | } 467 | /* 'rethrow(-777)' will not take place because of override */ 468 | /* 'rethrow(-321)' however does take place here */ 469 | 470 | So because 'throw(-777)' is not caught, it would have been 'rethrown' below 471 | its 'finally', but it is overridden by 'throw(-321)' inside its 'finally'. 472 | 473 | ### In the previous version you had to work with numbers instead of classes 474 | now. It costs me too much time to change this example right now. The 475 | mechanism has stayed the same however, so when you think classes you 476 | will still understand ... 477 | 478 | 479 | 480 | Signals 481 | ------- 482 | As part of executing the outermost 'try', the handlers of four ANSI C supported 483 | signals (SIGABRT, SIGFPE, SIGILL and SIGSEGV) are saved and replaced by a 484 | handler from this exception handling package. This handler causes a 'throw' 485 | to occur when one of these four signals is raised. The resulting exception 486 | is handled as any of the other exceptions. 487 | 488 | void Foo(void) 489 | { 490 | *((int *)0) = 0; /* causes SIGSEGV - may not be portable */ 491 | } 492 | 493 | void Boo(void) 494 | { 495 | try 496 | { 497 | Foo(); 498 | } 499 | catch (SegmentationFault, e) 500 | { 501 | printf("%s\n", e->getMessage()); 502 | } 503 | finally; 504 | } 505 | 506 | This will print a segmentation fault message. 507 | 508 | 509 | As part of the outermost 'finally', the signal handlers stored during the 510 | outermost 'try' will be restored again. In a multi-threading environment the 511 | threads/tasks either share the signal handlers (like on Solaris) or each have 512 | their private set (like on VxWorks); with shared handlers, restoration is 513 | delayed until the final thread leaves the exception handling scope (refer to 514 | "Multi-threading" below). 515 | 516 | When a signal exception is not caught and the signal handlers are restored, 517 | this signal is sent after all using raise(). When the signal handlers are not 518 | restored yet (may occur in a multi-threading environment with shared handlers), 519 | a message that the exception was lost is printed. 520 | 521 | 522 | 523 | Assertion Checking 524 | ------------------ 525 | The header "Assert.h" redefines the ANSI C assert() macro in order to let it 526 | generate a 'throw' on failure. Failed assertions are caught using EX_ASSERT. 527 | The exception specific data (retrieved with the member function getData()) 528 | points to a static character array containing the failed expression. 529 | 530 | Only when the preprocessor macro DEBUG is defined, will assert() expand to 531 | assertion checking code; without DEBUG defined, assert() is an empty macro. 532 | I experienced that, when building fault tolerant code, it can be very handy to 533 | have a macro that works as assert() during the test phase (i.e. when DEBUG is 534 | defined), but returns on failure during field operation. For this purpose I 535 | created a macro validate(). Note that validate() always leaves some code 536 | behind so use it sparingly where high performance is an issue. 537 | 538 | #include "Assert.h" 539 | 540 | void * ListRemoveHead(List *pList) 541 | { 542 | assert(pList != NULL); 543 | validate(pList->count > 0, NULL); 544 | ... 545 | } 546 | 547 | When in this example (taken from "List.c") is NULL, the caller has 548 | made a structural/severe error. I chose to use assert() because this kind of 549 | error should be found during tests and I don't want this test to slow down 550 | ListRemoveHead() when the software is released. On the other hand, trying to 551 | remove the head of an empty list (when pList->count == 0) is a more functional 552 | and less severe user error. It might for example be caused by a misunderstan- 553 | ding of how to use this list operation, or by an exceptional condition that 554 | occurred in a complex algorithm. By using validate(), the programmer will be 555 | notified during tests; but when DEBUG is no longer defined the ListRemoveHead() 556 | will return the reasonable value NULL instead of causing a program crash (some 557 | time later). 558 | 559 | When validate() is used in void function you must use NOTHING (defined in 560 | "Assert.h") as return value : 561 | 562 | void FactorStore(int alpha, int beta) 563 | { 564 | validate(alpha < beta, NOTHING); 565 | ... 566 | } 567 | 568 | 569 | When you want the functionality of validate() but rather like to throw an 570 | exception instead of returning, you can use the check() macro. 571 | 572 | 573 | When assert() is used outside an exception handling scope, it behaves as 574 | usual. In contrary with the ANSI C assert(), the default behavior of assert() 575 | (which is also used in validate() and check()) outside, is to not invoke the 576 | C library function abort() to terminate the process. On a failed assertion, 577 | which typically occurs in the test phase, I rather leave the program running 578 | to see what else happens. But if you do like to use abort(), simply define 579 | ASSERT_ABORT. 580 | 581 | 582 | 583 | Allocating Memory 584 | ----------------- 585 | Error checking code is (or should be) often seen around calls of malloc(), 586 | calloc() and realloc(): 587 | 588 | if ((pData = malloc(sizeof(DATA))) == NULL) 589 | return ERROR; 590 | 591 | The header file "Alloc.h" defines each of these operations as macro that per- 592 | form this check for you and throw an EX_MEMORY exception when out-of-memory: 593 | 594 | #include "Alloc.h" 595 | 596 | void Nice(void) 597 | { 598 | try 599 | { 600 | pData = malloc(sizeof(DATA)); 601 | } 602 | catch (OutOfMemoryError, e) 603 | { 604 | printf("%s\n", e->getMessage()); 605 | exit 1; 606 | } 607 | finally; 608 | } 609 | 610 | This will print an out-of-memory message when malloc() fails. 611 | 612 | 613 | For convenience a new() macro is also supplied which uses the C library routine 614 | calloc(). Because you can simply pass the type, it saves you from having to 615 | write sizeof() all the time: 616 | 617 | pData = new(DATA); 618 | 619 | 620 | These macros add only a small overhead (a function call and an if statement), 621 | so even when speed is an issue you may still want to use them. Note that the 622 | check is also performed when using these macros outside a critical code 623 | section; so using these macros you will always be notified when a memory allo- 624 | cation has failed. 625 | 626 | 627 | 628 | Multi-threading 629 | --------------- 630 | This package allows multiple threads/tasks, having a common address space, to 631 | concurrently use exception handling. From a user perspective almost every- 632 | thing stays the same compared to a single-threading application. The internal 633 | exception context that has to be kept for each thread is conveniently hidden 634 | for the user; [s]he is not confronted with obscure handlers as you would 635 | expext to see in a C implementation. 636 | 637 | Internally the thread ID is used as key for exception context lookup. 638 | Everything is done for you now (compared to previous version). You choose 639 | one of: EXCEPT_MT_SHARED EXCEPT_MT_PRIVATE, by adding a -D.... C-preprocessor 640 | flag. You also have to supply a function with which to get the current 641 | thread-ID and a function to perform a mutex lock...... 642 | 643 | As was mentioned above in "Signals": In a multi-threading environment the 644 | threads/tasks either share the signal handlers (like on Solaris) or each have 645 | their private set (like on VxWorks). Because exception handling has to decide 646 | if it must delay default signal handler restoration until the final thread 647 | leaves the exception handling scope (when handlers are shared), the user 648 | has to choose... see above. 649 | 650 | #include 651 | #include 652 | 653 | void Task(void) 654 | { 655 | try /* implicitly creates exception context */ 656 | { 657 | /* do something */ 658 | } 659 | catch (Throwable, e) 660 | { 661 | } 662 | finally; 663 | } 664 | 665 | void Launcher(void) 666 | { 667 | ex_threading_init(taskIdSelf, EX_PRIVATE_HANDLERS); 668 | 669 | taskSpawn("task1", MY_PRIORITY, 0, MY_STACK_SIZE, Task, 1); 670 | taskSpawn("task2", MY_PRIORITY, 0, MY_STACK_SIZE, Task, 2); 671 | taskSpawn("task3", MY_PRIORITY, 0, MY_STACK_SIZE, Task, 3); 672 | } 673 | 674 | Each thread has to invoke ex_thread_enter() before its first 'try' statement. 675 | When the thread ID was used earlier by another thread (some OSs recycle thread 676 | IDs), this routine destroys the outdated exception context if there. Normally 677 | this context is destroyed automatically, but will be left behind when 678 | for example killed by another thread. Adding 'outermost' 679 | just before the outermost 'try' prevents 680 | a memory leak, and makes sure that the exception handling package keeps track 681 | of the number of active threads, so it can restore shared signal handlers at 682 | the right moment. 683 | 684 | A thread must not use any of the exception handling macros (including return()), 685 | because this implicitly creates a new exception context for the thread and will 686 | probably result in a memory leak. 687 | 688 | When a thread kills another thread it should call ex_thread_cleanup() with the 689 | ID of the killed thread. This will have the same effect as ex_thread_leave(). 690 | In a multi-threading environment that recycles IDs, ex_thread_cleanup() may 691 | lead to hazardous situations when a new thread starts using the ID before the 692 | cleanup() has finished. To prevent this, thread scheduling should be disabled 693 | during killing the thread an the cleanup which follows immediately. You could 694 | also decide to not use ex_thread_cleanup() at all, because ex_thread_enter() 695 | takes care of the cleanup when the thread ID was used before. 696 | 697 | This is all there is to multi-threading, all the rest remains the same! 698 | 699 | ### (Almost) all ex_thread... calls are not needed any more in the current 700 | version! 701 | 702 | 703 | 704 | Preprocessor Flags 705 | ------------------ 706 | This section summarizes the C preprocessor flags and describes their effect 707 | when defined: 708 | 709 | DEBUG - switches on assertion checking and catch() checking 710 | ASSERT_ABORT - causes assert macros to invoke abort() 711 | EXCEPT_DEBUG - switches on printing debug messages in "Except.h" 712 | 713 | The EXCEPT_DEBUG flag is only used during development of the exception 714 | package. 715 | 716 | 717 | 718 | Compiler Warnings 719 | ----------------- 720 | When you use the 'return' macro inside a 'try', 'catch' or 'finally' block, 721 | your compiler may warn you: 722 | 723 | try 724 | { 725 | return(7); 726 | } 727 | catch (Throwable, e) /* line # 77 */ 728 | { 729 | printf("%s\n", e->getMessage()); 730 | } 731 | finally; 732 | 733 | --> "Test.c", line 77: warning: end-of-loop code not reached 734 | 735 | These warning are about the exception handling macro code and can be safely 736 | ignored. 737 | 738 | 739 | ### A number of these below have been solved/done. 740 | 741 | Possible Extensions & Modifications 742 | ----------------------------------- 743 | A. Shield non-user definitions in header files with #ifdef _EXCEPT_PRIVATE. 744 | 745 | B. Choose other name for global variables that will less likely clash with 746 | user names. 747 | 748 | C. Configurable central error message output, instead of printing to . 749 | 750 | D. Place last accessed hash table entry at the head of the node list to spead 751 | lookup in case killed threads are not cleaned up. 752 | 753 | E. Supply some mechanism for hierarchical user exception 'classes' (using 754 | bit masks: member in Except structure). In the current implementa- 755 | tion it is not very nice that you can only either catch a single user 756 | exception (with a negative number) or catch them all (with EX_THROW). 757 | 758 | F. Investigate if rethrow() can be eliminated in a clean way. Refer to the 759 | discussion above why it was introduced. 760 | 761 | G. A new() macro for arrays. The only 'problem' is choosing a nice name. 762 | 763 | 764 | 765 | Known Problems & Caveats and Todos 766 | ---------------------------------- 767 | A. Integers are assumed to be (at least) 32 bit. 768 | 769 | B. The code belonging to this package does not throw exception when out of 770 | memory. This is not a real issue since only small chunks of memory are 771 | allocated; when even these are not available, we're in a fatal situation. 772 | 773 | C. In multi-threading, the correct operation of this package fully depends on 774 | the application supplied routine that returns the thread ID. When it would 775 | return different IDs for the same thread, chaos will be the result. 776 | 777 | D. In multi-threading not invoking ex_thread_leave() or ex_thread_cleanup() 778 | for an old thread, results in a memory leak. 779 | 780 | E. The assert() and validate() macros are used by the package. Check what 781 | happens when each of these internal checks fails. We don't want recursion 782 | or crashes. This must be done before any final release. 783 | 784 | F. Some OSs mask the handled signal. Some restore this mask when the signal 785 | handler returns. Investigate if restoration takes place on every OS when 786 | the handler is left using longjmp(). 787 | 788 | G. A harsh assertion check scheme for ExceptTreadEnter/Leave() in 789 | ExceptGetContext(). Let ExceptGetContext() when ex_thread_enter() has not 790 | been invoked yet. Currently ExceptGetContext() is very nice and creates 791 | a new context when not there yet. 792 | 793 | H. Maybe clean up the way and ExceptGetContext() are used in "Except.c". 794 | Invoking ExceptGetContext() may not be strictly necessary at all places. 795 | 796 | I. Perform optimizations. The current implementation was made with a main 797 | focus on clearity. 798 | 799 | J. Some pointer variables (like exStack> and ) are misused 800 | as flag (i.e., they are NULL or not). Although this might make "Except.c" 801 | a bit more efficient, it also makes this code harder to understand/maintain. 802 | Finally, the routine ExceptFinally() requires some clean up. 803 | 804 | K. Better testing and code review for especially multi-threading is needed. 805 | Please have a close look and tell me what you find! 806 | 807 | L. Review (style, syntactics and semantics) of this README and the comments 808 | in the sources would also be appreciated. 809 | 810 | 811 | 812 | The Files 813 | --------- 814 | This package consists of the following files (they are formatted with a TAB 815 | width of 8 and use '\n' as end-of-line character (the UNIX way)): 816 | 817 | Alloc.c - Memory allocation module. Contains the private routines used 818 | by the optional memory allocation macros defined in "Alloc.h". 819 | Compile and link this file with your application when you use 820 | these macros. 821 | 822 | Alloc.h - Memory allocation module header. Include when you want to use 823 | the memory allocation macros. 824 | 825 | Except.c - Exception handling module. Contains the private routines and 826 | global variable definitions used by the exception handling 827 | macros defined in "Except.h". This file must be compiled and 828 | linked with your application. 829 | 830 | Except.h - Exception handling module header. Must be included. 831 | 832 | Hash.c - Hash table library. It is used by the exception handling 833 | package, so this file must be compiled and linked with your 834 | application. You can also use this easy library yourself. 835 | 836 | Hash.h - Hash table library header. Only needs to be included if you 837 | want to use this library yourself. 838 | 839 | Lifo.c - LIFO buffer library. It is used by the exception handling 840 | package, so this file must be compiled and linked with your 841 | application. You can also use this easy library yourself. 842 | 843 | Lifo.h - LIFO buffer library header. Only needs to be included if you 844 | want to use this library yourself. 845 | 846 | List.c - Doubly linked list library. It is used by the exception 847 | handling package, so this file must be compiled and linked with 848 | your application. You can also use this easy library 849 | yourself. It contains a lot more routines than used by this 850 | package. 851 | 852 | List.h - Doubly linked list library header. Only needs to be included 853 | if you want to use this library yourself. 854 | 855 | Test.c - The single-threaded test file. Can be used as a source of 856 | examples. (Multi-threading has been tested on Solaris the 857 | test file is not finished yet and is therefore not included.) 858 | 859 | README - Last but noy least, this very file. It describes how to use 860 | the package. Operation is explained in the source. 861 | 862 | 863 | 864 | >>>>>>>>>> That's all folks! <<<<<<<<<< 865 | --------------------------------------------------------------------------------