5. Errors
5.1 Introduction
 
                        While developing a program, errors are unacceptable mistakes done by programmer often referred to as bugs. To maintain the quality of a program, errors need be removed and a program should be free of errors to be considered acceptable.
The errors can be classified as follows:
| Type of Error | Found By | 
|---|---|
| Compile-time errors (Syntax, Type Errors) | Compiler | 
| Link-time errors | Linker (During combination of object files into an exceutable program) | 
| Run-time errors | Checks (Computer/ Library/ User Code) | 
| Logic errors | Programmer (Looking for the causes of erroneous results) | 
How to deal with errors to produce an acceptable software ?
Answer: Follow these 3 basic approaches
- Organize software to minimize errors
- Eliminate most of the errors we made through debugging and testing
- Make sure the remaining errors are not serious
5.2 Sources of errors
- Poor specification & Incomplete programs: Occurs when all cases are not handled including error cases.
- Unexpected arguments: 
                        Occurs when a argument is not handled in a function.
                         
 Example:sqrt(-1.2)
 Sincesqrt()of adoublereturnsdouble, there is no possible correct return value.
- Unexpected input: 
                        Occurs when input cases are not handled.
 Example: When user enters a string input instead of a expected integer input
- Unexpected state: Most programs keep a lot of data around for use by different parts of the system. What if such a data is wrong or incomplete?
- Logical errors: The code just doesn't do what you meant it to.
5.3 Compile-time errors
 
                        
                      Compiler analyzes to detect syntax errors and type
                      errors.
Many of the errors are simply "silly" errors
                      caused due to incomplete edits of the code or mistyping.
                    
5.3.1 Syntax errors
                        
                          int s1 = area(7; // error: ) missing
                          int s2 = area(7) // error: ; missing
                          Int s3 = area(7); // error: Int is not a type
                          int s4 = area('7); // error: non-terminated character (' missing)
                        
                        
                        
                        For example, given the error in the declaration of s3
                          above, a compiler is unlikely to say
                        “You misspelled int; don’t capitalize the
                        i.”
Rather, it’ll say something like
                        “syntax error: missing ‘;’
                        before identifier ‘s3’”
                        “‘s3’ missing storage-class or
                        type identifiers”
“‘Int’ missing
                        storage-class or type identifiers”
Drill
Write the code above in a file and see what errors are produced.
5.3.2 Type errors
After syntax errors, the compiler begins looking for type errors.
                        
                          int x0 = arena(7); // error: undeclared function
                          int x1 = area(7); // error: wrong number of arguments
                          int x2 = area("seven",2); // error: 1st argument has a wrong type
                        
                        
                        
                    5.3.3 Non-errors
                        
                          int x4 = area(10,–7); // OK: but what is a rectangle with a width of minus 7?
                        
                        
                        
                        
                          For x4, no error message from the
                          compiler. area() asks for two integers
                          irrespective of whether it is positive or negative.
                          Therefore, area (10,–7) is fine.
                        
5.4 Link-time errors
Translational units - several separately compiled parts in a program.
                    
int area(int length, int width); // calculate area of a rectangle
int main()
{
    int x = area(2,3);
}
                    
                    
                    
                    
                      The linker will complain that it didn't find a definition
                      area(), unless area() is defined
                       in some other source file and it is linked to the
                       generated code from the source file. 
Also, both the
                       return type and the argument types must be same as that
                       of the file and the definition of area().
                    
5.5 Run-time errors
 
                        
                      Even after the program is syntactically correct it may
                      exit unexpectedly during execution.
In such case it
                      encounters a runtime error.
We say, a program crashes
                      when it halts due to a runtime error.
                    
Examples:
- division by zero
- accessing a non-existing file, dictionary value, object attribute or list element
- using a non-defined identifier
- operation on incompatible types
                        
                          int area(int length, int width) // calculate area of a rectangle
                          {
                              return length*width;
                          }
                          int framed_area(int x, int y) // calculate area within frame
                          {
                              return area(x–2,y–2);
                          }
                          int main()
                          {
                              int x = –1;
                              int y = 2;
                              int z = 4;
                              // . .
                              int area1 = area(x,y);
                              int area2 = framed_area(1,z);
                              int area3 = framed_area(y,z);
                              double ratio = double(area1)/area3; // convert to double to get
                              // floating-point division
                          }
                        
                      
                    
                    
                      The above calls lead to negative values of areas,
                      being assigned to area1 and
                      area2.
                      Also, look at the calculation of the ratio in the code
                      above. It looks innocent enough.
                      Did you notice something wrong with it?
                      Look again: 
                      area3 will be 0,
                      so that double(area1)/area3 divides by zero.
                      
                      The above leads to a hardware-detected error
                      that terminates the program. This problem can be dealt in
                      two ways:
                      
- The caller of area()deal with bad arguments.
- area()(the called function) deal with bad arguments.
5.5.1 The caller deals with errors
                          Protect the call of area(x,y) in
                          main():
                          
                            
                              if (x<=0) error("non-positive x");
                              if (y<=0) error("non-positive y");
                              int area1 = area(x,y);
                            
                          
                        
                        
                          To complete protecting area()
                          from bad arguments, we have to deal with the calls
                          through framed_area():
                          
                            
          if (z<=2)
            error("non-positive 2nd area() argument called by framed_area()");
          int area2 = framed_area(1,z);
          if (y<=2 || z<=2)
            error("non-positive area() argument called by framed_area()");
          int area3 = framed_area(y,z);
                            
                          
                        
                        
                          This is messy,
                            but there is also something fundamentally
                            wrong.
                            What if someone modified framed_area()
                             to use 1 instead of 2?
                             
Someone doing that would have to look at
                            every call of framed_area() and modify
                             the error-checking code correspondingly.
                             Such code is called “brittle” because it breaks
                             easily.
                        
                          We could make the code less brittle by giving the
                          value subtracted by framed_area() a name:
                          
                            
            constexpr int frame_width = 2;
            int framed_area(int x, int y) // calculate area within frame
            {
            return area(x–frame_width,y–frame_width);
            }
                            
                          
                        
                        
                          That name could be used by code calling
                          framed_area():
                          
                            
        if (1–frame_width<=0 || z–frame_width<=0)
            error("non-positive argument for area() called by framed_area()");
        int area2 = framed_area(1,z);
        if (y–frame_width<=0 || z–frame_width<=0)
            error("non-positive argument for area() called by framed_area()");
        int area3 = framed_area(y,z);
                            
                          
                        
                        
                          Look at that code! 
                          Are you sure it is correct?
                            Do you find it pretty? Is it easy to read?
                          
                          Actually, we find it ugly (and therefore
                          error-prone).
                          We have more than trebled the size of the code
                          and exposed an implementation detail of
                          framed_area(). There
                          has to be a better way!
                        
Look at the original code:
                            
                              int area2 = framed_area(1,z);
                              int area3 = framed_area(y,z);
                            
                          
                        
                        
                          It may be wrong, but at least we can see what it is
                          supposed to do. We can keep this code if we
                          put the check inside framed_area().
                        
5.5.2 The callee deals with errors
                            
          int framed_area(int x, int y) // calculate area within frame
          {
            constexpr int frame_width = 2;
            if (x–frame_width<=0 || y–frame_width<=0)
              error("non-positive area() argument called by framed_area()");
            return area(x–frame_width,y–frame_width);
          }
                            
                          
                        
                        
                          This is rather nice, and we no longer have to write a
                          test for each call of framed_area().
                          Furthermore, if anything to do with the error
                          handling changes, we only have to modify the code
                          in one place.
                        
                            
          int area(int length, int width) // calculate area of a rectangle
          {
            if (length<=0 || width <=0) error("non-positive area() argument");
            return length*width;
          }
                            
                          
                        
                        
                          This will catch all errors in calls to
                          area(), so we no longer need to check
                          in framed_area().
                        
5.5.3 Error reporting
                          Once you have checked a set of arguments and
                          found an error, what should you do?
                          Sometimes you can return an “error value.” 
                          Example:
                          
        
        // ask user for a yes-or-no answer;
        // return 'b' to indicate a bad answer (i.e., not yes or no)
        char ask_user(string question)
        {
          cout << question << "? (yes or no)\n";
          string answer = " ";
          cin >> answer;
          if (answer =="y" || answer=="yes") return 'y';
          if (answer =="n" || answer=="no") return 'n';
          return 'b'; // ‘b’ for “bad answer”
        }
        // calculate area of a rectangle;
        // return –1 to indicate a bad argument
        int area(int length, int width)
        {
          if (length<=0 || width <=0) return –1;
          return length*width;
        }
        
                          
                        
                        That way, we can have the called function do the detailed checking, while letting each caller handle the error as desired.
This approach seems like it could work, but it has a couple of problems that make it unusable in many cases:
- Now both the called function and all callers must test. The caller has only a simple test to do but must still write that test and decide what to do if it fails.
- A caller can forget to test. That can lead to unpredictable behavior further along in the program.
- 
                              Many functions do not have an “extra” return
                              value that they can use to indicate an error.
                               For example, a function that reads an integer
                               from input (such as cin’s operator>>) can obviously return anyintvalue, so there is nointthat it could return to indicate failure.
There is another solution that deals with this problem: using exceptions.
5.6 Exceptions
                      If a function finds an error which it cannot handle,
                      it does not return normally. It throws an exception
                      indicating what went wrong.
                       
                          
- tryblock - lists all kinds of exceptions code needs to handle in the catch parts.
- catchblock - specifies what to do if called code uses throw.
5.6.1 Bad arguments
                            
                              class Bad_area { };   // a type specifically for reporting errors from area()
                              // calculate area of a rectangle;
                              // throw a Bad_area exception in case of a bad argument
                              int area(int length, int width)
                              {
                                if (length<=0 || width<=0) throw Bad_area{};
                                  return length*width;
                              }
                            
                          
                        
                        
                          Bad_area{} means “Make an object of type
                          Bad_area with the default value,”
                          throw Bad_area{} means “Make an object of
                           type Bad_area and throw it.”
                        
5.6.2 Range errors
                            
                              vector<int> v;    // a vector of ints
                              for (int i; cin>>I; )
                                  v.push_back(i);     // get values
                              for (int i = 0; i<=v.size(); ++i)     // print values
                                  cout << "v[" << i <<"] == " << v[i] << '\n';
                            
                          
                        
                        
                          The termination condition is i<=v.size()
                          rather than the correct i<v.size().
                          It is an example of an off-by-one error.
                        
5.6.3 Bad input
                          Consider reading a floating-point number:
                          
                            
                              double d = 0;
                              cin >> d;
                            
                          
                        
                        
                          Testing if the last input operation succeeded by
                          testing cin
                          
                            
                              if (cin) {
                                // all is well, and we can try reading again
                              }
                              else {
                                // the last read didn’t succeed, so we take some other action
                              }
                            
                          
                        
                        
                          One possible reason for operation failure is that
                          there wasn’t a double for
                          >> to read.
                          
                          
            double some_function()
            {
                double d = 0;
                cin >> d;
                if (!cin) error("couldn't read a double in 'some_function()'");
                // do something useful
            }
                          
                          
                        
                        
                          The condition !cin means that the
                          previous operation on cin failed.
                        
5.6.4 Narrowing errors
                          Narrowing errors are errors when we assign a value
                          that’s “too large to fit” to a variable, it is
                          implicitly truncated.
                          
                            
                              int x = 2.9;
                              char c = 1066;
                            
                          
                        
                        
                          Since ints don't have fractional values
                          of an integer, x will get the value 2 rather than
                          2.9.
                          
And c will get the value 42
                          (representing the character *) as per ASCII
                          character set.
                        
5.7 Logic errors
 
                        After removing initial compiler and linker errors, logic errors occur when the program runs but produces wrong output.
                        
                          int main()
                          {
                              vector<double> temps; // temperatures
                              for (double temp; cin>>temp; ) // read and put into temps
                                  temps.push_back(temp);
                              double sum = 0;
                              double high_temp = 0;
                              double low_temp = 0;
                              for (int x : temps)
                              {
                                  if(x > high_temp) high_temp = x; // find high
                                  if(x < low_temp) low_temp = x; // find low
                                  sum += x; // compute sum
                              }
                              cout << "High temperature: " << high_temp << '\n';
                              cout << "Low temperature: " << low_temp << '\n';
                              cout << "Average temperature: " << sum/temps.size() << '\n';
                          }
                        
                      
                    
                    
                      For values,
                      
                        76.5, 73.5, 71.0, 73.6, 70.1, 73.5, 77.6, 85.3,
                        88.5, 91.7, 95.9, 99.2, 98.2, 100.6, 106.3, 112.4,
                        110.2, 103.6, 94.9, 91.7, 88.4, 85.2, 85.4, 87.7
                      
                      The output produced was,
                      High temperature: 112.4
                      Low temperature: 0.0
                      Average temperature: 89.2
                      Since low_temp was initialized at
                      0.0, it would remain 0.0
                      unless one of the temperatures in the data was
                      below zero.
                    
5.8 Estimation
                      Estimation is a noble art that combines common
                      sense and some very simple arithmetic applied to a few
                      facts.
                      Also sometimes humorously called as guesstimation.
                      
                      Always ask yourself these questions:
                      
- Is this answer to this particular problem plausible?
- How would I recognize a plausible result?
                      Here, we are not asking,
                      “What’s the exact answer?” or
                      “What’s the correct answer?”
                      That’s what we are writing the program to tell us.
                    
5.9 Debugging
 
                        
                      When you have written some code, you have to find and
                      remove the errors. That process is usually called
                      debugging and the errors are called bugs.
                      
                      Debugging works roughly like this:
                      
- Get the program to compile.
- Get the program to link.
- Get the program to do what it is supposed to do.
Debugging the most tedious and time-wasting aspect of programming and will go to great lengths during design and programming to minimize the amount of time spent hunting for bugs.
5.9.1 Practical debug advice
 
                            
                          Make the program easy to read so that you have a
                          chance of spotting the bugs:
                          
- Comment your code well
- The name of the program
- The purpose of the program
- Who wrote this code and when
- Version numbers
- What complicated code fragments are supposed to do
- What the general design ideas are
- How the source code is organized
- What assumptions are made about inputs
- What parts of the code are still missing and what cases are still not handled
- Use meaningful names
- Use a consistent layout of code
- Break code into small functions, each expressing a logical action
- Avoid complicated code sequences
- Try to avoid nested loops, nested if-statements, complicated conditions
- Use library facilities rather than your own code when you can
- A library is likely to be better thought out and better tested than what you could produce as an alternative while busily solving your main problem
5.10 Pre- and post-conditions
 
                        
                      Pre-condition Example:
                      
                        
                          int my_complicated_function(int a, int b, int c)
                          // the arguments are positive and a < b < c
                          {
                              if (!(0<a && a<b && b<c)) // ! means “not” and && means “and”
                                  error("bad arguments for mcf");
                              // . . .
                          }
                        
                      
                    
                    
                      
                        int x = my_complicated_function(1, 2, "horsefeathers");
                      
                      
                    
                    
                      Here, the compiler will catch that the requirement
                      (“pre-condition”) that the third argument be an integer
                      was violated.
                      Basically, what we are talking about here is what to do
                      with the requirements/pre-conditions that the compiler
                      can’t check.
                    
5.10.1 Post-conditions
                            
                              // calculate area of a rectangle;
                              // throw a Bad_area exception in case of a bad argument
                              int area(int length, int width)
                              {
                                  if (length<=0 || width <=0) throw Bad_area();
                                      return length*width;
                              }
                            
                          
                        
                        
                          It checks its pre-condition,
                          but it doesn’t state it in the comment
                          (that may be OK for such a short function) and
                          it assumes that the computation is correct
                          (that’s probably OK for such a trivial computation).
                          
However, we could be a bit more explicit:
                        
                            
                              int area(int length, int width)
                              // calculate area of a rectangle;
                              // pre-conditions: length and width are positive
                              // post-condition: returns a positive value that is the area
                              {
                                  if (length<=0 || width <=0) error("area() pre-condition");
                                      int a = length*width;
                                  if (a<=0) error("area() post-condition");
                                      return a;
                              }
                            
                          
                        
                        We couldn’t check the complete post-condition, but we checked the part that said that it should be positive.
5.11 Testing
 
                        
                      Testing is a systematic way to search for errors.
                      “The last bug” is a programmers’ joke.
                      There is no “the last bug” in a large program.
                      
                      Testing includes comparing the results to what is
                      expected by executing a program with a large and
                      systematically selected set of inputs.
                    
Test Yourself!
 
                        - The four major types of errors are...
- header errors, variable errors, function errors and class errors
- compile-time, link-time, run-time, and logical
- object errors, inheritance errors, template errors and polymorphism errors
- A linker error would occur when...
- A function used in your source code can't be found in any linked file or library.
- A variable should be linked by assignment to another variable but it is not.
- A link between one class and another is missing.
- An example of a run-time error is when...
- you send the wrong type of argument to a function.
- you meant to add tax to the sales price but you subtracted it instead.
- your program tries to divide by zero.
- you forget the semi-colon at the end of a line of code.
- An example of a logic error is when...y
- you send the wrong type of argument to a function.
- you meant to add tax to the sales price but you subtracted it instead.
- your program tries to divide by zero.
- you forget the semi-colon at the end of a line of code.
- One reason why throwing an exception is better than returning an error value is...
- the exception cannot be ignored.
- throwing an exception makes the code that produced the error also handle it.
- throwing exceptions is a more modern style.
- An example of something student programs are not expected to handle is:
- bad input
- hardware failures
- divide-by-zero errors
- Most large programs...
- will always contain some bugs.
- can be fully de-bugged in a couple of days.
- will likely be bug-free from the start.
- Which is used to handle the exceptions in c++?
- exception handler
- catch handler
- none of the mentioned
- handler
- Which of the following does not cause a syntax error to be reported by the C++ compiler?
- Extra blank lines
- Missing ; at the end of a statement
- Missing */ in a comment
- Mismatched {}
- Which of the following is not a syntax error?
- std::cout << "Hello world! ";
- std::cout << 'Hello world! ';
- std::cout << "Hello world! ";
- Run Time Errors are ..
- the errors which are traced by the compiler during compilation, due to wrong grammar for the language used in the program
- the errors encountered during execution of the program, due to unexpected input or output
- the errors encountered when the program does not give the desired output
- What will happen when the exception is not caught in the program?
- error
- program will execute
- none of the mentioned
- block of that code will not execute
Answers
1. b; 2. a; 3. c; 4. b; 5. a; 6. b; 7. a; 8. a; 9. a; 10. c; 11. b; 12. a;
Drill
