📄 seh.gml
字号:
_try {
if( ctr == 2 ) invoke_finally_block() continue;
if( ctr == 3 ) invoke_finally_block() break;
}
.millust end
.np
There is some overhead associated with local unwinds such as that
incurred by the use of
.kw break,
.kw continue,
.kw return,
etc.
To avoid this overhead, a new transfer keyword called
.kw _leave
can be used.
The use of this keyword causes a jump to the end of the
.us try block.
Consider the following modified version of an earlier example.
.exam begin
#include <stdio.h>
#include <stdlib.h>
#include <excpt.h>
void main( int argc, char **argv )
{
read_file( fopen( argv[1], "r" ) );
}
.exam break
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;
}
.exam break
_try {
for(;;) {
line++;
if( fgets( buffer, 255, input ) == NULL ) break;
icode = buffer[0];
if( icode != '1' ) _leave;
x = buffer[1];
line++;
if( fgets( buffer, 255, input ) == NULL ) _leave;
icode = buffer[0];
if( icode != '2' ) _leave;
y = buffer[1];
process( x, y );
}
printf( "Processing complete\n" );
fclose( input );
input = NULL;
}
.exam break
_finally {
if( input != NULL ) {
printf( "Invalid sequence: line = %d\n", line );
fclose( input );
}
}
}
.exam break
void process( char x, char y )
{
printf( "processing pair %c,%c\n", x, y );
}
.exam end
.np
There are two ways to enter the
.us finally
block.
One way is caused by unwinds &mdash. either local (by the use of
.kw break,
.kw continue,
.kw return,
or
.kw goto
.ct )
or global (more on global unwinds later).
The other way is through the normal flow of execution (i.e., simply by
falling through the bottom of the
.us try
block).
There is a function called
.kw AbnormalTermination
that can be used to determine which of these two methods was used
to enter the
.us finally
block.
If the function returns
.id TRUE
(1)
then the
.us finally
block was entered using the first method;
if the function returns
.id FALSE
(0)
then the
.us finally
block was entered using the second method.
This information may be useful in some circumstances.
For example, you may wish to avoid executing any code in a
.us finally
block if the block was entered through the normal flow of execution.
.exam begin
#include <stdio.h>
#include <stdlib.h>
#include <excpt.h>
void main( int argc, char **argv )
{
read_file( fopen( argv[1], "r" ) );
}
.exam break
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;
}
.exam break
_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" );
}
.exam break
_finally {
if( AbnormalTermination() )
printf( "Invalid sequence: line = %d\n", line );
fclose( input );
}
}
.exam break
void process( char x, char y )
{
printf( "processing pair %c,%c\n", x, y );
}
.exam end
.np
In the above example, we reverted back to the use of the
.kw return
statement since the execution of a
.kw _leave
statement is considered part of the normal flow of execution
and is not considered an "abnormal termination" of the
.us try
block.
Note that since it is not possible to determine whether the
.us finally
block is executing as the result of a local or global unwind,
it may not be appropriate to use the
.kw AbnormalTermination
function as a way to determine what has gone on.
However, in our simple example, we expect that nothing could go wrong
in the "processing" routine.
.*
.section Exception Filters and Exception Handlers
.*
.np
We would all like to create flawless software but situations arise for
which we did not plan.
An event that we did not expect which causes the software to cease to
function properly is called an exception.
The computer can generate a hardware exception when the software
attempts to execute an illegal instruction.
We can force this quite easily in C by dereferencing a NULL pointer as
shown in the following sample fragment of code.
.exam begin
char *nullp = NULL;
*nullp = '\1';
.exam end
.np
We can also generate software exceptions from software by calling a
special function for this purpose.
We will look at software exceptions in more detail later on.
.np
Given that exceptions are generally very difficult to avoid in large
software projects, we can acknowledge that they are a fact of life and
prepare for them.
A mechanism similar to
.us try/finally
has been devised that makes it possible to gain control when an
exception occurs and to execute procedures to handle the situation.
.np
The exception handling mechanism involves the pairing up
of a
.kw _try
block with an
.kw _except
block.
This is illustrated in the following example.
.exam begin
#include <stdio.h>
#include <stdlib.h>
#include <excpt.h>
void main( int argc, char **argv )
{
char *nullp = NULL;
.exam break
printf( "Attempting illegal memory reference.\n" );
_try {
*nullp = '\1';
}
_except (EXCEPTION_EXECUTE_HANDLER) {
printf( "Oh no! We had an exception!\n" );
}
printf( "We recovered fine...\n" );
}
.exam end
.np
In this example, any exception that occurs while executing "inside"
the
.us try
block will cause the
.us except
block to execute.
Unlike the
.us finally
block, execution of the
.us except
block occurs only when an exception is generated and only when the
expression after the
.kw _except
keyword evaluates to
.ix 'EXCEPTION_EXECUTE_HANDLER'
.id EXCEPTION_EXECUTE_HANDLER.
The expression can be quite complex and can involve the execution of a
function that returns one of the permissible values.
The expression is called the exception "filter" since it determines
whether or not the exception is to be handled by the
.us except
block.
The permissible result values for the exception filer are:
.begnote $break
.note EXCEPTION_EXECUTE_HANDLER
.ix 'EXCEPTION_EXECUTE_HANDLER'
meaning "I will handle the exception".
.note EXCEPTION_CONTINUE_EXECUTION
.ix 'EXCEPTION_CONTINUE_EXECUTION'
meaning "I want to resume execution at the point where the exception
was generated".
.note EXCEPTION_CONTINUE_SEARCH
.ix 'EXCEPTION_CONTINUE_SEARCH'
meaning "I do not want to handle the exception so continue looking
down the
.us try/except
chain until you find an exception handler that does want to handle the
exception".
.endnote
.*
.section Resuming Execution After an Exception
.*
.np
Why would you want to resume execution of the instruction that caused
the exception?
Since the exception filter can involve a function call, that function
can attempt to correct the problem.
For example, if it is determined that the exception has occurred
because of the NULL pointer dereference, the function could modify the
pointer so that it is no longer NULL.
.exam begin
#include <stdio.h>
#include <stdlib.h>
#include <excpt.h>
char *NullP = NULL;
.exam break
int filter( void )
{
if( NullP == NULL ) {
NullP = malloc( 20 );
return( EXCEPTION_CONTINUE_EXECUTION )
}
return( EXCEPTION_EXECUTE_HANDLER )
}
.exam break
void main( int argc, char **argv )
{
printf( "Attempting illegal memory reference.\n" );
_try {
*NullP = '\1';
}
.exam break
_except (filter()) {
printf( "Oh no! We had an exception!\n" );
}
printf( "We recovered fine...\n" );
}
.exam end
.np
Unfortunately, this is does not solve the problem.
Understanding why it does not involves looking at the sequence of
computer instructions that is generated for the expression in
question.
.millust begin
*NullP = '\1';
mov eax,dword ptr _NullP
mov byte ptr [eax],01H
.millust end
.pc
The exception is caused by the second instruction which contains a
pointer to the referenced memory location (i.e., 0) in register EAX.
.ix 'EXCEPTION_CONTINUE_EXECUTION'
This is the instruction that will be repeated when the filter returns
.id EXCEPTION_CONTINUE_EXECUTION.
Since EAX did not get changed by our fix, the exception will reoccur.
Fortunately,
.id NullP
is changed and this prevents our program from looping forever.
The moral here is that there are very few instances where you can
correct "on the fly" a problem that is causing an exception to occur.
Certainly, any attempt to do so must involve a careful inspection of
the computer instruction sequence that is generated by the compiler
(and this sequence usually varies with the selection of compiler
optimization options).
The best solution is to add some more code to detect the problem
before the exception occurs.
.*
.section Mixing and Matching _try/_finally and _try/_except
.*
.np
Where things really get interesting is in the interaction between
.us try/finally
blocks and
.us try/except
blocks.
These blocks can be nested within each other.
In an earlier part of the discussion, we talked about global unwinds
and how they can be caused by exceptions being generated in nested
function calls.
All of this should become clear after studying the following example.
.tinyexam begin
#include <stdio.h>
#include <stdlib.h>
#include <excpt.h>
void func_level4( void )
{
char *nullp = NULL;
printf( "Attempting illegal memory reference\n" );
_try {
*nullp = '\1';
}
_finally {
if( AbnormalTermination() )
printf( "Unwind in func_level4\n" );
}
printf( "Normal return from func_level4\n" );
}
.tinyexam break
void func_level3( void )
{
_try {
func_level4();
}
_finally {
if( AbnormalTermination() )
printf( "Unwind in func_level3\n" );
}
printf( "Normal return from func_level3\n" );
}
.tinyexam break
void func_level2( void )
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -