#include #include #include #include /* for clock() */ /* static size of cleanup stack */ #define MAX_SLOTS 3000 #define INLINE_CLEANUP #ifndef NO_EXCEPTIONS /********************************************************************/ /********************************************************************/ /***** *****/ /***** THE EXCEPTION SUB-SYSTEM IMPLEMENTATION *****/ /***** *****/ /***** this is a drastically simple implementation but shows *****/ /***** that the implementation of cleanup-stack based SEH is *****/ /***** not trivial. *****/ /***** *****/ /***** Any real-world implementation should be thread-safe *****/ /***** and use dynamic allocation to manage the cleanup *****/ /***** stack.. *****/ /***** *****/ /********************************************************************/ /********************************************************************/ /* cleanup stack types */ typedef void* cleanup_item_t; typedef void (*cleanup_func_t)( cleanup_item_t, const void* ); typedef int cleanup_mark_t; typedef struct { cleanup_item_t item; cleanup_func_t cleanup_func; const void* cleanup_data; void* pad; } cleanup_slot_t; typedef struct { cleanup_slot_t items[ MAX_SLOTS ]; cleanup_slot_t* top; cleanup_slot_t* limit; } cleanup_stack_t; /* exception types */ typedef int exception_t; #define ERR_OK 0 #define ERR_ALLOC 1 #define ERR_DIVIDE_BY_ZERO 2 typedef struct exception_handler_t exception_handler_t; struct exception_handler_t { exception_handler_t* previous; jmp_buf jump_buffer; cleanup_mark_t mark; exception_t exception; }; /* the TRY macro */ #define TRY \ { \ exception_handler_t __x_handler; \ \ exception_handler_set(&__x_handler); \ if ( setjmp( __x_handler.jump_buffer ) == 0 ) \ { \ /* the CATCH macro */ #define CATCH(_e_) \ } \ else \ { \ exception_t _e_ = exception_handler_unset( &__x_handler ); /* the END_CATCH macro */ #define END_CATCH \ } \ if ( __x_handler.exception == ERR_OK ) \ exception_handler_unset( &__x_handler ); \ } /* the THROW and RETHROW macros */ #define THROW(x) exception_throw(x) #define RETHROW(x) exception_throw(x) /* forward declaration */ extern void exception_throw( exception_t e ); /***************************************************************/ /* */ /* Implementing the cleanup stack */ /* */ /* the cleanup stack itself */ static cleanup_stack_t cleanup_stack; /* initialise cleanup stack */ void cleanup_init( void ) { cleanup_stack.top = cleanup_stack.items; cleanup_stack.limit = cleanup_stack.items + MAX_SLOTS; } /* push new element on top of the stack */ void cleanup_push( const void* item, cleanup_func_t cleanup_func, const void* cleanup_data ) { cleanup_slot_t* top = cleanup_stack.top; if ( top < cleanup_stack.limit ) { top->item = (cleanup_item_t)item; top->cleanup_func = cleanup_func; top->cleanup_data = cleanup_data; cleanup_stack.top = top+1; } else { /* no more items can be pushed (equivalent to memory exhaustion) */ /* first, cleanup the item itself.. */ if (item) cleanup_func( (cleanup_item_t)item, cleanup_data ); /* then, throw exception */ THROW( ERR_ALLOC ); } } /* pop top-most element */ void cleanup_pop( const void* item, int keep_it ) { cleanup_slot_t* top = --cleanup_stack.top; if ( top < cleanup_stack.items || top->item != item ) { /* the stack is empty, something weird happened */ fprintf( stderr, "invalid cleanup stack state detected !!" "\nAborting immediately\n" ); exit(1); } if ( !keep_it ) top->cleanup_func( top->item, top->cleanup_data ); } /* return current stack height */ cleanup_mark_t cleanup_mark( void ) { return (cleanup_mark_t)(cleanup_stack.top - cleanup_stack.items); } /* unwind stack */ void cleanup_unwind( cleanup_mark_t mark ) { cleanup_slot_t* new_top = cleanup_stack.items + mark; cleanup_slot_t* top = cleanup_stack.top - 1; if ( new_top > top || mark < 0 ) { /* an invalid mark value was passed */ fprintf( stderr, "invalid cleanup stack state detected. Your program" "is severely buggy !!\nAborting immediately\n" ); exit(1); } cleanup_stack.top = new_top; for ( ; top > new_top; top-- ) { if ( top->item ) top->cleanup_func( top->item, top->cleanup_data ); } } /***************************************************************/ /* */ /* Implementing exception handling */ /* */ /* the current exception handler - this is normally thread-specific */ static exception_handler_t* current_handler = NULL; void exception_handler_set( exception_handler_t* handler ) { handler->exception = 0; handler->previous = current_handler; current_handler = handler; handler->mark = cleanup_mark(); } /* unlink exception handler, return exception if any */ exception_t exception_handler_unset( exception_handler_t* handler ) { exception_t result; if ( handler != current_handler ) { /* something strange happened */ fprintf( stderr, "invalid exception sub-system internal state. Aborting\n" ); exit(2); } current_handler = handler->previous; handler->previous = NULL; return handler->exception; } /* throw an exception */ void exception_throw( exception_t exc ) { exception_handler_t* handler = current_handler; if (!handler) { /* no handler was set when this exception occured */ fprintf( stderr, "uncaught fatal exception %d. Aborting\n", exc ); } /* store exception code, cleanup the stack, then transfer control */ handler->exception = exc; cleanup_unwind( handler->mark ); longjmp( handler->jump_buffer, 1 ); } /***************************************************************/ /* */ /* Support routines */ /* */ /* allocate a new block of memory and push it on top of the */ /* cleanup stack.. */ /* */ void* malloc_push( size_t size ) { void* p = NULL; if ( size > 0 ) { p = malloc(size); if ( !p ) { THROW(ERR_ALLOC); return NULL; } /* now push on top of the cleanup stack */ #ifndef INLINE_CLEANUP cleanup_push( p, (cleanup_func_t)free, NULL ); #else { /* inlined version, for speed */ cleanup_slot_t* top = cleanup_stack.top; if ( top < cleanup_stack.limit ) { top->item = (cleanup_item_t)p; top->cleanup_func = (cleanup_func_t)free; cleanup_stack.top = top+1; } else { /* no more items can be pushed (equivalent to memory exhaustion) */ /* first, cleanup the item itself.. */ if (p) free(p); /* then, throw exception */ THROW( ERR_ALLOC ); } } #endif } return p; } /* pop and free */ void pop_free( void* block ) { #ifndef INLINE_CLEANUP cleanup_pop( block, 1 ); free( block ); #else cleanup_slot_t* top = --cleanup_stack.top; if ( top < cleanup_stack.items || top->item != block || top->cleanup_func != (cleanup_func_t)free ) { /* the stack is empty, something weird happened */ fprintf( stderr, "invalid cleanup stack state detected !!" "\nAborting immediately\n" ); exit(1); return; } free( block ); #endif } #else /* NO_EXCEPTIONS */ # define malloc_push(x) malloc(x) # define pop_free(x) free(x) #endif /* NO_EXCEPTIONS */ /********************************************************************/ /********************************************************************/ /***** *****/ /***** THE TESTING CODE *****/ /***** *****/ /********************************************************************/ /********************************************************************/ /***************************************************************/ /* */ /* Timing code.. */ /* */ /* SunOS 4.1.* does not define CLOCKS_PER_SEC, so include */ /* to get the HZ macro which is the equivalent. */ #if defined(__sun__) && !defined(SVR4) && !defined(__SVR4) #include #define CLOCKS_PER_SEC HZ #endif /* return time in milliseconds */ double get_time( void ) { return clock() * 1000.0 / CLOCKS_PER_SEC; } /***************************************************************/ /* */ /* The tests.. */ /* */ /* This is really simple, we first allocate 1 temporary */ /* memory block and free it, we then allocate 2 blocks, */ /* free them, allocate 3 blocks, etc.. */ /* */ #define MAX_TEMPS 5000 /* must be > MAX_SLOTS */ #define BLOCK_SIZE 32 static void* temps[ MAX_TEMPS ]; static int count = 0; /* the test, with no checks at all */ static void do_test_1( void ) { int n, i; count = 0; for ( n = 1; n < MAX_TEMPS; n++ ) { /* allocate 'i' temporary memory blocks */ for ( i = 0; i < n; i++ ) { /* when no exceptions are compiled in, we must abort when */ /* MAX_SLOTS is attained to perform the exact same number */ /* of allocations and frees. These result in accurate */ /* timings for comparisons.. */ if ( i == MAX_SLOTS ) { /* we need to release the current blocks to mimic exactly */ /* the behaviour of test 2 */ for ( --i; i >= 0; i-- ) free( temps[i] ); return; } temps[i] = malloc( BLOCK_SIZE ); count++; } /* now, release them (in order) */ for ( i = n-1; i >= 0; i-- ) free( temps[i] ); } } #ifndef NO_EXCEPTIONS /* perform test with exceptions */ static void do_test_2( void ) { int n, i; count = 0; for ( n = 1; n < MAX_TEMPS; n++ ) { /* allocate 'i' temporary memory blocks */ for ( i = 0; i < n; i++ ) { temps[i] = malloc_push( BLOCK_SIZE ); count++; } /* now, release them (in order) */ for ( i = n-1; i >= 0; i-- ) pop_free( temps[i] ); } } #endif /* !NO_EXCEPTIONS */ /* the same test than do_test_1, except that everything is checked */ static void do_test_3( void ) { int n, i; count = 0; for ( n = 1; n < MAX_TEMPS; n++ ) { for ( i = 0; i < n; i++ ) { if ( i == MAX_SLOTS ) { Error: for ( --i; i >= 0; i-- ) free( temps[i] ); return; } temps[i] = malloc( BLOCK_SIZE ); if ( temps[i] == NULL ) goto Error; count++; } /* now, release them (in order) */ for ( i = n-1; i >= 0; i-- ) free( temps[i] ); } } int main( void ) { double t0, t1; /* initialise sub-system */ cleanup_init(); /* first, do test without exceptions */ t0 = get_time(); do_test_1(); t0 = get_time() - t0; fprintf( stderr, "%10d alloc/free without checks = %6.3f s\n", count, t0/1000.0 ); t0 = get_time(); do_test_3(); t0 = get_time() - t0; fprintf( stderr, "%10d alloc/free with checks = %6.3f s\n", count, t0/1000.0 ); #ifndef NO_EXCEPTIONS /* then, the test with exceptions */ t1 = get_time(); TRY { do_test_2(); } CATCH(e) { switch (e) { case ERR_ALLOC: /* printf( "memory exhaustion for count = %d\n", count ); */ break; default: printf( "exception %d occured ??\n", e ); } } END_CATCH t1 = get_time() - t1; fprintf( stderr, "%10d alloc/free with exceptions = %6.3f s\n", count, t1/1000.0 ); fprintf( stderr, " difference = %6.3f s (%.0f%%)\n", (t1-t0)/1000.0, t0 > 0 ? ((t1-t0)/t0)*100.0 : 0.0 ); #endif /* !NO_EXCEPTIONS */ /* return now */ return 0; }