Monday 10 June 2013

C++ Exception, Error Management & Debugging


All programs have bugs. The bigger the program, the more bugs, and many of those bugs actually "get out the door" and into final released software. The biggest expense in many major programming efforts is testing and fixing.The least expensive problems or bugs to fix are the ones you manage to avoid creating. Following coding guidelines is the easiest way to avoid bugs. Code rot is a phenomenon when code deteriorates due to being neglected and a perfectly well-written, fully tested code develops new and bizarre behavior six months after it is released. 

Hence the golden rule is to avoid the bugs in first place, then have enough testability logic built in the code to easily trap error and lastly have a mitigation plan to gracefully exit in case an uncorrectable error occur. Following picture depicts the same.


The first topics I want to address based on the philoshpy of avoiding errors is proper use of C++ exception.
As per definition an exception is an object that is passed from the area of code where a problem occurs to the part of the code that is going to handle the problem. The type of the exception determines which area of code will handle the problem. Exceptions provide an express path from the code that allocates resources to the code that can handle the error condition.
Try Catch block
  • To catch exceptions we must place a portion of code under exception inspection or try block. 
  • Catch matches the data type of the exception object to catch a particular exception. Syntax shown below.
Catch statement
The exception handler is declared with the catch keyword.The type of this parameter is very important (Used for matching). Multiple catch statements can be chained ellipsis (...) as the parameter of catch can be used to exception any type An exception can be re thrown if you do not know what to do with the exception.
Exception Specification
When declaring a function we can limit the exception type it might directly or indirectly throw by appending a throw suffix to the function declaration:
                float myfunction (char param) throw (int);
               //The only exception that this function might throw is an exception of type int

If this throw specifier is left empty with no type, this means the function is not allowed to throw exceptions.
              int myfunction (int param) throw(); // no exceptions allowed
Functions with no throw specifier (regular functions) are allowed to throw exceptions with any type
             int myfunction (int param); // all exceptions allowed

unexected()
If your exception specification claims you’re going to throw a certain set of exceptions and then you throw something that isn’t in that set
set_unexpected( )
unexpected( ) is implemented with a pointer to a function, so you can change. Example of unexpected is shown in this file.unexpectedException.cpp

Exception Matching
When an exception is thrown, the exception-handling system looks through the "nearest" handlers in the order they are written. Matching happens for the base class first.
Example: exceptionmatching.cpp

Standard Exception
The C++ Standard library provides a base class specifically designed to declare objects to be thrown as exceptions. This class has the usual default and copy constructors, operators and destructors, plus an additional virtual member function called what that returns a null-terminated character sequence (char *). All exceptions thrown by components of the C++ Standard library throw exceptions derived from std::exception class(bad_alloc, bad_cast , bad_exception , bad_typeid , ios_base::failure )

Programming with exceptions
When to avoid exceptions
  • Not for asynchronous events
  • Not for ordinary error conditions
  • Not for flow-of-control (Costly in terms of performance)
  • New exceptions, old code
Some Guidelines
  • Always use exception specifications
  • Start with standard exceptions
  • Nest your own exceptions
  • Use exception hierarchies
  • Catch by reference, not by value
  • Throw exceptions in constructors
    • Because a constructor has no return value 
    • continuing execution after construction fails in a C++ program is a guaranteed disaster

  • Don’t raise exceptions in destructors

Moving to next topic, error handling can be enabled via asserts as well. Some details on assert based on my experience are provided below.
Error Handling - Asserts
Assert is a macro that returns true if its parameter evaluates to true and takes some action if it evaluates to false. It expand to zero code if NDEBUG macro is defined. The action taken on assert failure is compiler defined. User defined Asserts can also be used if used in case some custom action is desired.
#ifdef NDEBUG
#define ASSERT(x)
#else
#define ASSERT(x) \
    if (! (x)) \
    { \
           cout << "ERROR!! Assert " << #x << " failed\n"; \
           cout << " on line " << __LINE__  << "\n"; \
           cout << " in file " << __FILE__ << "\n";  \
     }
#endif

Asserts Vs Exception
Assert is not designed to handle run time error but it should be used to catch programming errors. When an assert hits you know there is a bug in the code. Assert works only in the debug mode but exceptions will work in both debug and release mode. Asserts have zero overhead in release mode but asserts have finite overhead.

Trace Macros
As mentioned in teh start of this post, we need to build in enough testiblity logic in the code to debug and isolate issue very quickly in the code. Using various form pf trace is on such mechanism. But if trace is not put in properly, it will impact the speed of actual programs. Trace macros can help in this regard to selectively turn on trace only when required.

#define TRACE(ARG) cout << #ARG << endl; ARG
TRACE(for(int i = 0; i < 100; i++))
TRACE(  cout << i << endl;)

Another Variant of trace macro
#define D(a) cout << #a "=[" << a << "]" << endl;

Logging to Trace File
#ifdef TRACEON
ofstream TRACEFILE__("TRACE.OUT");
#define cout TRACEFILE__
#endif

C style Trace Macro formatted
#define LOG_DBG_MESSAGE_FORMATTED(format,...)\
    if(m_trace_log_handle)\
    {\
    fprintf(m_trace_log_handle,(format),__VA_ARGS__);\
    fflush(m_trace_log_handle);\
    }\

Tracing using interface: Any interface based implementation can also be traced using decorator design pattern. Please visit my other post to get more details. Link

Summing Up

Ideally any application should display the error messages to the users during its execution itself as nobody prefers looking a manual or error guide. Trace files should available to debug any issue at the users site and using a debugger(VC++ IDE, GDB) should be last resort as it is not always possible to attach the debugger to sources due to various reasons. Runtime log should be built in the main application code so that they are easy to enable when required and the trace code should be designed so that there is very little or no overhead.
Hope you find this post useful. Feel free to provide any feedback/suggestion you may have.






No comments:

Post a Comment