Retro video games delivered to your door every month!
Click above to get retro games delivered to your door ever month!
X-Hacker.org- Watcom C/C++ User's Guide - we begin our look at seh with a simple model. in this model, there are two http://www.X-Hacker.org [<<Previous Entry] [^^Up^^] [Next Entry>>] [Menu] [About The Guide]
We begin our look at SEH with a simple model.  In this model, there are two
blocks of code - the "guarded" block and the "termination" block.  The
termination code is guaranteed to be executed regardless of how the
"guarded" block of code is exited (including execution of any "return"
statement).


     _try {
         /* guarded code */
         .
         .
         .
     }
     _finally {
         /* termination handler */
         .
         .
         .
     }

The _finally block of code is guaranteed to be executed no matter how the
guarded block is exited ( break,  continue,  return,  goto, or longjmp() ).
Exceptions to this are calls to abort(),  exit() or _exit() which terminate
the execution of the process.

There can be no intervening code between try and finally blocks.

The following is a contrived example of the use of _try and _finally.

Example:

     #include <stdio.h>
     #include <stdlib.h>
     #include <excpt.h>

     int docopy( char *in, char *out )
     {
       FILE        *in_file = NULL;
       FILE        *out_file = NULL;
       char        buffer[256];


       _try {
         in_file = fopen( in, "r" );
         if( in_file == NULL ) return( EXIT_FAILURE );
         out_file = fopen( out, "w" );
         if( out_file == NULL ) return( EXIT_FAILURE );

         while( fgets((char *)buffer, 255, in_file) != NULL ) {
           fputs( (char *)buffer, out_file );
         }
       }
       _finally {
         if( in_file != NULL ) {
           printf( "Closing input file\n" );
           fclose( in_file );
         }
         if( out_file != NULL ) {
           printf( "Closing output file\n" );
           fclose( out_file );
         }
         printf( "End of processing\n" );
       }
       return( EXIT_SUCCESS );
     }


     void main( int argc, char **argv )
     {
       if( argc < 3 ) {
         printf( "Usage: mv [in_filename] [out_filename]\n" );
         exit( EXIT_FAILURE );
       }
       exit( docopy( argv[1], argv[2] ) );
     }

The try block ignores the messy details of what to do when either one of the
input or output files cannot be opened.  It simply tests whether a file can
be opened and quits if it cannot.  The finally block ensures that the files
are closed if they were opened, releasing the resources associated with open
files.  This simple example could have been written in C without the use of
SEH.

There are two ways to enter the finally block.  One way is to exit the try
block using a statement like return.  The other way is to fall through the
end of the try block and into the finally block (the normal execution flow
for this program).  Any code following the finally block is only executed in
the second case.  You can think of the finally block as a special function
that is invoked whenever an exit (other than falling out the bottom) is
attempted from a corresponding try block.

More formally stated, a local unwind occurs when the system executes the
contents of a finally block because of the premature exit of code in a try
block.

+--------------------------------------------------------------------------+
|   Note:  Kevin Goodman describes "unwinds" in his article.  "There are   |
| two types of unwinds:  global and local.  A global unwind occurs when    |
| there are nested functions and an exception takes place.  A local unwind |
| takes place when there are multiple handlers within one function.        |
|  Unwinding means that the stack is going to be clean by the time your    |
| handler's code gets executed."                                           |
+--------------------------------------------------------------------------+
The try/finally structure is a rejection mechanism which is useful when a
set of statements is to be conditionally chosen for execution, but not all
of the conditions required to make the selection are available beforehand.
 It is an extension to the C language.  You start out with the assumption
that a certain task can be accomplished.  You then introduce statements into
the code that test your hypothesis.  The try block consists of the code that
you assume, under normal conditions, will succeed.  Statements like if ...
 return can be used as tests.  Execution begins with the statements in the
try block.  If a condition is detected which indicates that the assumption
of a normal state of affairs is wrong, a return statement may be executed to
cause control to be passed to the statements in the finally block.  If the
try block completes execution without executing a return statement (i.e.,
all statements are executed up to the final brace), then control is passed
to the first statement following the try block (i.e., the first statement in
the finally block).

In the following example, two sets of codes and letters are read in and some
simple sequence checking is performed.  If a sequence error is detected, an
error message is printed and processing terminates; otherwise the numbers
are processed and another pair of numbers is read.

Example:

     #include <stdio.h>
     #include <stdlib.h>
     #include <excpt.h>

     void main( int argc, char **argv )
     {
       read_file( fopen( argv[1], "r" ) );
     }


     void read_file( FILE *input )
     {
       int         line = 0;
       char        buffer[256];
       char        icode;
       char        x, y;

       if( input == NULL ) {
         printf( "Unable to open file\n" );
         return;
       }


       _try {
         for(;;) {
           line++;
           if( fgets( buffer, 255, input ) == NULL ) break;
           icode = buffer[0];
           if( icode != '1' ) return;
           x = buffer[1];
           line++;
           if( fgets( buffer, 255, input ) == NULL ) return;
           icode = buffer[0];
           if( icode != '2' ) return;
           y = buffer[1];
           process( x, y );
         }
         printf( "Processing complete\n" );
         fclose( input );
         input = NULL;
       }


       _finally {
         if( input != NULL ) {
           printf( "Invalid sequence: line = %d\n", line );
           fclose( input );
         }
       }
     }


     void process( char x, char y )
     {
         printf( "processing pair %c,%c\n", x, y );
     }

The above example attempts to read a code and letter.  If an end of file
occurs then the loop is terminated by the break statement.

If the code is not 1 then we did not get what we expected and an error
condition has arisen.  Control is passed to the first statement in the
finally block by the return statement.  An error message is printed and the
open file is closed.

If the code is 1 then a second code and number are read.  If an end of file
occurs then we are missing a complete set of data and an error condition has
arisen.  Control is passed to the first statement in the finally block by
the return statement.  An error message is printed and the open file is
closed.

Similarly if the expected code is not 2 an error condition has arisen.  The
same error handling procedure occurs.

If the second code is 2, the values of variables x and y are processed
(printed).  The for loop is repeated again.

The above example illustrates the point that all the information required to
test an assumption (that the file contains valid pairs of data) is not
available from the start.  We write our code with the assumption that the
data values are correct (our hypothesis) and then test the assumption at
various points in the algorithm.  If any of the tests fail, we reject the
hypothesis.

Consider the following example.  What values are printed by the program?

Example:

     #include <stdio.h>
     #include <stdlib.h>
     #include <excpt.h>

     void main( int argc, char **argv )
     {
       int ctr = 0;


       while( ctr < 10 ) {
         printf( "%d\n", ctr );
         _try {
           if( ctr == 2 ) continue;
           if( ctr == 3 ) break;
         }
         _finally {
           ctr++;
         }


         ctr++;
       }
       printf( "%d\n", ctr );
     }

At the top of the loop, the value of ctr is 0.  The next time we reach the
top of the loop, the value of ctr is 2 (having been incremented twice, once
by the finally block and once at the bottom of the loop).  When ctr has the
value 2, the continue statement will cause the finally block to be executed
(resulting in ctr being incremented to 3), after which execution continues
at the top of the while loop.  When ctr has the value 3, the break statement
will cause the finally block to be executed (resulting in ctr being
incremented to 4), after which execution continues after the while loop.
 Thus the output is:


     0
     2
     3
     4

The point of this exercise was that after the finally block is executed, the
normal flow of execution is resumed at the break,  continue,  return, etc.
 statement and the normal behaviour for that statement occurs.  It is as if
the compiler had inserted a function call just before the statement that
exits the try block.

     _try {
       if( ctr == 2 ) invoke_finally_block() continue;
       if( ctr == 3 ) invoke_finally_block() break;
     }

There is some overhead associated with local unwinds such as that incurred
by the use of break,  continue,  return, etc.  To avoid this overhead, a new
transfer keyword called _leave can be used.  The use of this keyword causes
a jump to the end of the try block.  Consider the following modified version
of an earlier example.

Example:

Online resources provided by: http://www.X-Hacker.org --- NG 2 HTML conversion by Dave Pearson