O hateful error, melancholy's child!
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
|
| |
| 0x0000 | everything ok |
| 0x0010 | file I/O error in the stream operator |
| 0x0020 | division by zero |
| 0x0040 | Heap error: |
| 0x0080 | overflow in function or operator |
| 0x0100 | underflow in function or operator |
| 0x0200 | an argument of a function is uninitialized or has an illegal value |
| 0x0400 | incorrect base passed as argument to a constructor |
| 0x0800 | even modulus in |
| 0x1000 | NULL pointer passed as argument |
| 0x2000 | call to an uninitialized pseudorandom number generator |
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 }
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.
18.116.50.87