Chapter 16. Error Handling

O hateful error, melancholy's child!

—Shakespeare, Julius Caesar

(Don't) Panic ...

The C++ functions presented in the foregoing chapters embody mechanisms for analyzing whether during the execution of a called C function an error or other situation has occurred that requires a particular response or at least a warning. The functions test whether the passed variables have been initialized and evaluate the return value of the called C functions:

LINT f (LINT arg1, LINT arg2)
{
  LINT result;
  int err;

  if (arg1.status == E_LINT_INV)
    LINT::panic (E_LINT_VAL, "f", 1, __LINE__);
  if (arg2.status == E_LINT_INV)
    LINT::panic (E_LINT_VAL, "f", 2, __LINE__);
  // Call C function to execute operation; error code is stored in err
  err = f_l (arg1.n_l, arg2.n_l, result.n_l);
  switch (err)
    {
      case 0:
        result.status = E_LINT_OK;
        break;
      case E_CLINT_OFL:
        result.status = E_LINT_OFL;
        break;
      case E_CLINT_UFL:
        result.status = E_LINT_UFL;
        break;
      default:
        LINT::panic (E_LINT_ERR, "f", err, __LINE__);
    }
  return result;
}

If the variable status contains the value E_LINT_OK, then this is the optimal case. In less happy situations, in which overflow or underflow has occurred in a C function, the variable status is set to the appropriate value E_LINT_OFL or E_LINT_UFL. Since our C functions already react to an overflow or underflow with a reduction modulo Nmax + 1 (cf. page 20), in such cases the functions terminate normally. The value of the variable status can then be queried with the member function

LINT_ERRORS LINT::Get_Warning_Status (void);

Furthermore, we have seen that the LINT functions always call a function with the well-chosen name panic() when the situation gets too hot to handle. The task of this member function is first of all to output error messages, so that the user of the program is made aware that something has gone awry, and secondly to ensure a controlled termination of the program. The LINT error messages are output via the stream cerr, and they contain information about the nature of the error that has occurred, about the function that has detected the error, and about the arguments that have triggered the error. In order that panic() be able to output all of this information, such information arriving from the calling function must be delivered, as in the following example:

LINT::panic (E_LINT_DBZ, "%", 2, __LINE__);

Here it is announced that a division by zero in the operator "%" has appeared in the line specified by the ANSI macro __LINE__, caused by the operator's argument number 2. The arguments are indicated as follows: 0 always denotes the implicit argument of a member function, and all other arguments are numbered from left to right, beginning with 1. The LINT error routine panic() outputs error messages of the following type:

Example 1. Use of an uninitialized LINT object as argument.

critical run-time error detected by class LINT:
Argument 0 in Operator *= uninitialized, line 1997
ABNORMAL TERMINATION

Example 2. Division by a LINT object with the value 0.

critical run-time error detected by class LINT:
Division by zero, operator/function/, line 2000
ABNORMAL TERMINATION

The functions and operators of the LINT class recognize the situations listed in Table 16-1.

Table 16-1. LINT function error codes

Code

Value

Explanation

E_LINT_OK

0x0000

everything ok

E_LINT_EOF

0x0010

file I/O error in the stream operator << or >>

E_LINT_DBZ

0x0020

division by zero

E_LINT_NHP

0x0040

Heap error: new returns the NULL pointer

E_LINT_OFL

0x0080

overflow in function or operator

E_LINT_UFL

0x0100

underflow in function or operator

E_LINT_VAL

0x0200

an argument of a function is uninitialized or has an illegal value

E_LINT_BOR

0x0400

incorrect base passed as argument to a constructor

E_LINT_MOD

0x0800

even modulus in mexpkm()

E_LINT_NPT

0x1000

NULL pointer passed as argument

E_LINT_RIN

0x2000

call to an uninitialized pseudorandom number generator

User-Defined Error Handling

As a rule, it is necessary to adapt error handling to particular requirements. The LINT class offers support in this regard in that the LINT error function panic() can be replaced by user-defined functions. Additionally, the following function is called, which takes as argument a pointer to a function:

void
LINT::Set_LINT_Error_Handler (void (*Error_Handler)
      (LINT_ERRORS, const char*, int, int, const, char*))
{
  LINT_User_Error_Handler = Error_Handler;
}

The variable LINT_User_Error_Handler is defined and initialized in flintpp.cpp as

static void (*LINT_User_Error_Handler)
(LINT_ERRORS, const char*, int, int, const char*) = NULL;

If this pointer has a value other than NULL, then the specified function is called instead of panic(), and it contains the same information as panic() would have. With respect to the implementation of a user-defined error-handling routine one has a great deal of freedom. But one must realize that the errors reported by the class LINT usually signal program errors, which are irreparable at run time. It would make no sense to return to the program segment in which such an error has occurred, and in general, in such cases the only reasonable course of action is to terminate the program.

The return to the LINT error routine panic() is effected by a call to

LINT::Set_LINT_Error_Handler(NULL);

The following example demonstrates the integration of a user-defined function for error handling:

#include "flintpp.h"
void my_error_handler (LINT_ERRORS err, const char* func,
                       int arg, int line, const char* file)
{
  //... Code
}
main()
{
  // activation of the user-defined error handler:
  LINT::Set_LINT_Error_Handler (my_error_handler);
  // ... Code
  // reactivate the LINT error handler:
  LINT::Set_LINT_Error_Handler (NULL);
  // ... Code
 }

LINT Exceptions

The exception mechanism of C++ is an instrument that is easier to utilize and thereby more effective for error handling than the methods offered by C. The error routine LINT::panic() described previously is limited to the output of error messages and the controlled termination of a program. In general, we are less interested in the division function in which a division by zero has occurred than the function that has called the division and thereby precipitated the error, information that LINT::panic() does not contain and thus cannot pass along. In particular, it is impossible with LINT::panic() to return to this function in order to remove an error there or to react in a way specific to the function. Such possibilities, on the other hand, are offered by the exception mechanism of C++, and we would like here to create the conditions that will make this mechanism usable for the LINT class.

Exceptions in C++ are based principally on three types of constructs: the try block, the catch block, and the instruction throw, by means of which a function signals an error. The first, the catch block, has the function of a local error-handling routine for the try block: Errors that occur within a try block and are announced by means of throw will be caught by the catch block, which follows the try block. Further instructions of the try block are then ignored. The type of error is indicated by the value of the throw instruction as parameter of the accompanying expression.

The connection between try and catch blocks can be sketched as follows:

try
  {
   ...  // If an error is signaled within an operation with
   ...  // throw, then it can be
   ...  // caught by the following catch block.
  }
...
catch (argument)
  {
   ...  // here follows the error handling routine.
  }

If an error does not occur directly within a try block but in a function that is called from there, then this function is terminated, and control is returned to the calling function until, by following the chain of calls in reverse order, a function within a try block is reached. From there control is passed to the appropriate catch block. If no try block is found, then the generic error routine appended by the compiler is called, which then terminates the program, usually with some nonspecific output.

It is clear what the errors are in the LINT class, and it would be a simple possibility to call throw with the error codes, which are provided to the panic() routine by the LINT functions and operators. However, the following solution offers a bit more comfort: We define an abstract base class

class LINT_Error
{
  public:
    char* function, *module;
    int argno, lineno;
    virtual void debug_print (void) const = 0; // pure virtual
    virtual ~LINT_Error() {function = 0; module = 0;};
};

as well as classes of the following type that build on it:

// division by zero
class LINT_DivByZero : public LINT_Error
{
  public:
    LINT_DivByZero (const char* func, int line, const char* file);
    void debug_print (void) const;
};
LINT_DivByZero::LINT_DivByZero (const char* func, int line, const char* file)
{
  module = file;
  function = func;
  lineno = line;
  argno = 0;
}
void LINT_DivByZero::debug_print (void) const
{
  cerr << "LINT-Exception:" << endl;
  cerr << "division by zero in function "
        << function << endl;
  cerr << "module: " << module << ", line: "
        << lineno << endl;
}

For every type of error there exists such a class that like the example shown here can be used with

throw LINT_DivByZero(function, line);

to report this particular error. Among others, the following subclasses of the base class LINT_Error are defined:

class LINT_Base : public LINT_Error // invalid basis
{ ...  };
class LINT_DivByZero : public LINT_Error // division by zero
{ ...  };
class LINT_EMod : public LINT_Error // even modulus for mexpkm
{ ...  };
class LINT_File : public LINT_Error // error with file I/O
{ ...  };
class LINT_Heap : public LINT_Error // heap error with new
{ ...  };
class LINT_Init : public LINT_Error // function argument illegal or uninitialized
{ ...  };
class LINT_Nullptr : public LINT_Error // null pointer passed as argument
{ ...  };
class LINT_OFL : public LINT_Error // overflow in function
{ ...  };
class LINT_UFL : public LINT_Error // underflow in function
{ ...  };

With this we are in a position, on the one hand, to catch LINT errors without distinguishing specifically which error has occurred by inserting a catch block

catch (LINT_Error const &err) // notice: LINT_Error is abstract
  {
    // ...
    err.debug_print();
    // ...
  }

after a try block, while on the other hand we can carry on a goal-directed search for an individual error by specifying the appropriate error class as argument in the catch instruction.

One should note that as an abstract base class LINT_Error is not instantiatable as an object, for which reason the argument err can be passed only by reference and not by value. Although all the LINT functions have been equipped with the panic() instruction for error handling, the use of exceptions does not mean that we must alter all the functions. Rather, we integrate the appropriate throw instructions into the panic() routine, where they are called in conjunction with the error that has been reported. Control is then transferred to the catch block, which belongs to the try blockofthe calling function. The following code segment of the function panic() clarifies the modus operandi:

void LINT::panic (LINT_ERRORS error, const char* func,
                  int arg, int line, const char* file)
{
  if (LINT_User_Error_Handler)
    {
      LINT_User_Error_Handler (error, func, arg, line, file);
    }
else
    {
      cerr << "critical run-time error detected by the
                              class LINT:
";
      switch (error)
        {
          case E_LINT_DBZ:
            cerr << "division by zero, function " << func;
            cerr << ", line " << line << ", module " << file << endl;
#ifdef LINT_EX
            throw LINT_DivByZero (func, line, file);
#endif
            break;
            // ...
       }
    }
}

The behavior that results in the case of an error can be completely controlled by user-defined routines for error handling without the necessity of intervention into the LINT implementation. Moreover, the exception handling can be completely turned off, which is necessary when this mechanism is not supported by a C++ compiler that is to be used. In the case of the present panic() function the exceptions must be turned on explicitly via the definition of the macro LINT_EX, such as with the compiler option -DLINT_EX. Some compilers require the specification of additional options for exception handling to be activated.

To close, we present a small demonstration of the LINT exceptions:

#include "flintpp.h"
main(void)
{
  LINT a = 1, b = 0;
  try
    {
      b = a / b;// error: division by 0
    }
  catch (LINT_DivByZero error) // error handling for division by 0
    {
      error.debug_print ();
      cerr << "division by zero in the module" << __FILE__
          << ", line " << __LINE__;
    }
}

Translated with GNU gcc by a call to

gcc -fhandle-exceptions -DLINT_EX divex.cpp flintpp.cpp flint.c -lstdc++

the program produces, in addition to the error message of the function panic(), the following output:

LINT-Exception:
division by zero in operator/function /
module: flintpp.cpp, line: 402
division by zero in module divex.cpp, line 17

The significant difference between this and standard error handling without exceptions is that we discover by means of the catch routine where the error was actually caused, namely in line 17 of the module divex.cpp, even though it was discovered somewhere else entirely, namely in the module flintpp.cpp. For debugging large programs this is an extremely helpful source of information.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.50.87