INFORMATION FROM DR. EBRAHIMI'S C++ PROGRAMING EASY WAYS

Home                  ABOUT US                    CONTACT US                  TUTORIAL

CHAPTER 19

ERRORS, EXCEPTION HANDLING, AND SOFTWARE ENGINEERING

 

While your program is running, you may encounter a problem that is unexpected. This problem can bring down the whole system. For example, your program may want to access a file that does not exist or, is in another directory. What would happen if your program is computing the average of a series of numbers, but the counter (denominator) is zero or becomes zero, or what happens to your program when it requests more memory than compiler can provide? Obviously these are unexpected situations that rarely happen and if they occur in an unguarded program, they may cause severe consequences. The strategy to handle errors is to provide a systematic way of detecting them and take the necessary actions to fix them and recover the program.

 

You may have already taken proper measures to combat the errors in your program simply by using an if-statement accompanied by a display message. The introduction of the assert( ) function from header #include <cassert> is more advanced and takes advantage of the system error messages and aborts the program upon an unwanted situation.  One might argue that the assert( ) function will not be able to recover from the bug. Rather than aborting the program, you can take a different action when the bug occurs. A more sophisticated and systematic approach for error handling is known as exception handling that was introduced by ANSI-C++ standard and added to C++. It is called an exception since it is unlikely to happen, but when it does happen it may produce unwanted results. Exception handling consists of enclosing the code that may result in an exception in a try block and if an exception occurs it is thrown. The statement that handles the exception is inside the catch block. After an exception is thrown (raised), normal control flow will suspend and the program will search for a match or a portion of the program that can handle the exception. After the exception is handled the program will either terminate or resume normal execution after the corresponding catch block.

Use of exception handling can separate error reporting from error handling and as a result handling errors becomes systematic and readable.

 

In conclusion, use of exception handling does not guarantee that errors will not occur. There is no guaranteed solution in preventing errors. One should consider having a fault tolerance system.

 

 

CATEGORIES OF BUGS

 

Programming bugs (errors) can be categorized into three kinds: compile time, run time, and logical errors. Syntax or compile time errors are known as grammatical errors that are detected by the compiler. Examples of syntax errors are misspelling the keywords, omitting the necessary punctuation such as a semicolon, or not balancing the quotation, parenthesis, or statement pairs. Run-time errors are errors that occur during the execution of the program when it attempts to perform an operation that the system cannot perform (illegal). A popular example of a run-time error is when a program tries to divide a number by zero.  Logical errors produce the wrong output, which is caused by applying inappropriate algorithms; for example when subtraction is used instead of addition.  One analogy of a logical error would be driving east when intending to go west, and the driver will never reach the desired destination if given the wrong directions.  Remember, in order for a program to produce output, the program has passed the stage of syntax and run-time error checking. Logical errors are the most difficult to detect and they are often confused with run-time errors.  If there is a run-time error, the system will terminate or an exception will take place. Linkage errors might result when linking two separately compiled programs (functions). Duplication of variable names is an example of a linkage error. There are different opinions as to what is considered a logical error and what is not. Errors can be categorized with respect to the compiler as follows: lexical errors, such errors resulting from misspelling; syntactic errors, such as unbalanced quotations or parentheses; semantic errors, such as applying an arithmetic operator to incompatible variables (data types), and logical errors, such as calling a recursive function without an exit (infinite).

 

 

SYNTAX VERSUS SEMANTIC ERRORS

 

Another form of error that is often detected by a compiler is known as a semantic (meaning) error. Syntax errors violate the language structure rules while semantic errors are syntactically correct but meaningless. An example of a semantic error is to perform arithmetic operations on 2 non-numeric variables, such as multiplication of 2 strings. Another example of a semantic error is performing arithmetic operations on the names of 2 functions (procedures) that do not return useful values. If the compiler does not detect a semantic error, it becomes a run-time error/logical error that is difficult to detect. For instance, associating an else to the wrong if statement, or applying the wrong precedence of arithmetic operators.

 

A common semantic error which is not detectable by compiler is using = when = = is the intended meaning. The semantic errors are more about understanding the meaning of the language constructs as opposed to problem solving. Logical errors are referred to as problem solving errors than errors resulting from language construct semantics.

 

 

SHOULD COMPILER ERROR MESSAGES BE TRUSTED

 

A compiler’s job is to detect any errors that violate the language structure and report the error to the user. If there are no errors then it converts the program to a low level language (object code) so that it can be executed. Detecting the error and reporting it accurately isn’t an easy task for compilers. Beginners put all their trust in the compiler because it is seen an important program that interprets programs. Due to incorrect and confusing error messages, dependence on the compiler’s messages diminishes. With experience, error messages can be interpreted and found useful. One may wonder why a compiler cannot report errors accurately given all the technology we have. The following are some explanations: the line number reported may not be accurate due to the fact that the compiler has already proceeded to the next statement.   Another reason for inaccuracy in error reporting is that the statement that contains the error has been eliminated, leading to more errors.  

 

 

RUN-TIME ERROR - FILE DOES NOT EXIST: SIMPLE ERROR CHECKING

 

The following program demonstrates how error checking can be simply performed by placing an if statement with a report message. The program below displays a message if the file does not exist and exits the execution rather than leaving it to the system to crash. 

Text Box: #include<fstream>
#include<iostream>
using namespace std;
 int main(){
            int id;
            ifstream fin ("pay.dat");
            if (fin==NULL){ cerr<<"CANNOT OPEN THE FILE"<<endl; exit(1); }//IF
            fin>>id;
            cout<<"ID IS: "<<id<<endl;
            return 0;           
}//MAIN

  

 

 

 

 

 

 

 

 

 

 

Text Box: Figure 19.1a – Checking if the file exists

  

 

Text Box: CANNOT OPEN THE FILE
Text Box: Figure 19.1b – Output of Figure 19.1a
 

 

 


 

                                                                                   

 

DIVIDE BY ZERO: A SIMPLE ERROR HANDLING PROGRAM

 

The program below illustrates a simple error check for divide-by-zero. An if statement determines if the denominator is zero and based on that a message is displayed and the program is aborted.

Text Box: #include<iostream>
using namespace std;
int main(){
            int m,n;
            cout<<"ENTER TWO NUMBERS TO DIVIDE: ";
            cin>>m>>n;
            if(n==0) {cerr<<"CANNOT DIVIDE BY ZERO"<<endl  ;abort();}//IF
            else cout<<"QUOTIENT IS: "<<m/n<<endl;
            return 0;           
}//MAIN

  

 

 

 

 

 

 

 

 

 

 

Text Box: ENTER TWO NUMBERS TO DIVIDE: 6 0
CANNOT DIVIDE BY ZERO
Text Box: Figure 19.2a – Program to check for divide by zero
Text Box: Figure 19.2b – Output of Figure 19.2a
 

 

 

 

 

 

 

 


 

OTHER RUN TIME ERRORS

 

In the preceding programs, we demonstrated two run-time errors: when the file does not exist and when the program divides by zero. Other run time errors are:

 

INSUFFICIENT MEMORY: This error results when a program tries to allocate memory dynamically, and there is not enough available memory such as:

phw=new int [100000];

 

INFINITE RECURSION: Behind the scenes of a recursive function is the stack. On each call to the function, values such as parameters and local variables are pushed into the stack and on the return the values are popped from the stack. If the recursive function never terminates, values will be pushed to the stack with every call and at some point the stack will be exhausted (overflowed) and crash will occur.

 

 

RUN-TIME ERROR: READING PASSED END OF FILE

 

A common run time error for novice programmers is when reading input data, especially from a data file, trying to read passed the end of the file. For example, what would happen if there are only 3 input data in the data file and you try to read 5 data in your program? One would expect that only 3 values would be read and the program would request the additional data however this is not the case. The following program was run on Visual C++6.0, GNU C++, and TURBO C++ 3.0.  In all cases, the last data in the file repeated for the remainder of the loop, even after the end of file was reached. Imagine if a payroll system had a loop for writing checks and the end of file was reached before the loop ended. In this case the last person in the data file would keep receiving checks until the loop ends. There are several solutions to this problem. The user can check the   

cin.eof( ) to break the loop, or check the status of the other input stream member functions such as: bad( ), fail( ), or good( ). Note that these functions return a Boolean value.

 

 

 

 

Text Box: #include<fstream>
#include<iostream>
using namespace std;
int main(){
            ifstream fin("employees.txt",ios::in);
            char fname[20], lname[20];
            for(int i=0; i<10; i++){
                        fin>>fname>>lname;
                        cout<<fname<<" "<<lname<<" ";
                        cout<<"1,000.00"<<endl;}//FOR
return 0;
}//MAIN
Text Box: Alireza Ebrahimi
Jason Smith
Robert Williams
Jane Doe

  

 

 

 

 


 

                                                                                                             

Text Box: Figure 19.3a – Run-time error of reading past end of file
Text Box: Figure 19.3c – employees.txt
 


 

 

Text Box: Alireza Ebrahimi 1,000.00
Jason Smith 1,000.00
Robert Williams 1,000.00
Jane Doe 1,000.00
Jane Doe 1,000.00
Jane Doe 1,000.00
Jane Doe 1,000.00
Jane Doe 1,000.00
Jane Doe 1,000.00
Jane Doe 1,000.00

                                                                                                                         

                                                                                                

 

                                                                                               

 

 

 

 

Text Box: Figure 19.3b – Output of Figure 19.3a

  

 

 

 


 

C++ DEBUGGER: BREAKPOINTS, WATCHING VARIABLES, SINGLE STEPPING

 

Most of the C++ compilers (Visual C++, Borland C++, and CodeWarrior) come with an Integrated Development Environment known as IDE, providing an environment with many features including editing, compiling, and a debugging program. With a debugger one can run the program and watch the values or set breakpoints and observe how the values of the variables change as stepping through the code. With the help of a debugger one can detect and correct the bugs. Using a breakpoint in a program will enable one to execute the program up to the breakpoint and see the code line by line and inspect variable values.   Every compiler has it’s own debugger that might be a little different than others. If one can’t use the debugger one can debug a program by inserting cout statements to display the contents of the variable. For example one can insert a cout statement inside a loop, if statement, or a function to inspect the control flow and check the content of a variable. Comments /*   */ can be used to isolate the problem in the program. If a Unix C++ compiler (e.g. g++ , GNU ) is used the debugging information can be obtained by typing the command man. On Dos/Windows environments main compilers are from: Borland, Microsoft, and CodeWarrior. Among the popular C++ compilers for the Macintosh environment are Symantec C++ and CodeWarrior. 

 

 

ASSERTION AS A DEBUGGING TOOL

 

In order for a program to continue, the assertion must be true at the point where the    assert( ) function is placed. For an assertion to be true, the parameter passed to the assert function must be true. If the assertion fails (false), the execution will be aborted and the message generated by the language will be displayed. The assert function can be used as a debugging tool to trace a program at any given point in the program.

 

The assert routine (macro) takes an expression (argument) that either evaluates to true or false. The directive #include <cassert> must be included to use assert( ). If there is no additional need for the assertion in debugging the program, assert( ) can be disabled by simply having a #define NDEBUG before the assert include header.

 

               
           
Hosted by www.Geocities.ws

1