Which is used to throw an exception in C++?


C++ Exceptions

When executing C++ code, different errors can occur: coding errors made by the programmer, errors due to wrong input, or other unforeseeable things.

When an error occurs, C++ will normally stop and generate an error message. The technical term for this is: C++ will throw an exception (throw an error).


C++ try and catch

Exception handling in C++ consist of three keywords: try, throw and catch:

The try statement allows you to define a block of code to be tested for errors while it is being executed.

The throw keyword throws an exception when a problem is detected, which lets us create a custom error.

The catch statement allows you to define a block of code to be executed, if an error occurs in the try block.

The try and catch keywords come in pairs:

Example

try {
  // Block of code to try
  throw exception; // Throw an exception when a problem arise
}
catch () {
  // Block of code to handle errors
}

Consider the following example:

Example

try {
  int age = 15;
  if (age >= 18) {
    cout << "Access granted - you are old enough.";
  } else {
    throw (age);
  }
}
catch (int myNum) {
  cout << "Access denied - You must be at least 18 years old.\n";
  cout << "Age is: " << myNum;
}

Try it Yourself »

Example explained

We use the try block to test some code: If the age variable is less than 18, we will throw an exception, and handle it in our catch block.

In the catch block, we catch the error and do something about it. The catch statement takes a parameter: in our example we use an int variable (myNum) (because we are throwing an exception of int type in the try block (age)), to output the value of age.

If no error occurs (e.g. if age is 20 instead of 15, meaning it will be be greater than 18), the catch block is skipped:

You can also use the throw keyword to output a reference number, like a custom error number/code for organizing purposes:

Example

try {
  int age = 15;
  if (age >= 18) {
    cout << "Access granted - you are old enough.";
  } else {
    throw 505;
  }
}
catch (int myNum) {
  cout << "Access denied - You must be at least 18 years old.\n";
  cout << "Error number: " << myNum;
}

Try it Yourself »


Handle Any Type of Exceptions (...)

If you do not know the throw type used in the try block, you can use the "three dots" syntax (...) inside the catch block, which will handle any type of exception:

Example

try {
  int age = 15;
  if (age >= 18) {
    cout << "Access granted - you are old enough.";
  } else {
    throw 505;
  }
}
catch (...) {
  cout << "Access denied - You must be at least 18 years old.\n";
}

Try it Yourself »



On Time RTOS-32

RTFiles-32

RTUSB-32

RTKernel for DOS

About On Time

Testimonials

Customers

Partners

On Time in the Press

Privacy Policy

Clean error handling without overhead

by Tom Schotland and Peter Petersen

Error handling is an important issue in embedded systems, and it can account for a substantial portion of a project's code. We were faced with this issue during the design of RTFiles, the embedded filesystem component of On Time RTOS-32, our Win32-compatible RTOS for 32-bit x86 targets. The core filesystem is portable with a C function API to the application and a device-driver interface below it. Typically, errors can occur in device drivers and must be reported to the application with suitable return codes, so errors must travel through the complete core filesystem.

The classic C approach to this problem is return codes. Each function returns a value indicating success or failure. However, with a nontrivial function call hierarchy, this approach clutters the code significantly. Every function must check the return code of every function call it makes and take care of errors. In most cases, the function will merely pass any errors back up to its caller. RTFiles has several hundred internal functions and a call hierarchy up to about 15 levels deep, so this approach would have been a nightmare to maintain.

Programming languages such as Ada or C++ address this issue with exceptions. Exceptions make it easy to separate error handling from the rest of the code. Intermediate functions can completely ignore errors occurring in functions they call, if they can't handle them anyway. Exceptions are much easier to maintain than error return codes, so we definitely wanted to use them for RTFiles. Unfortunately, we had to write RTFiles in C, and not C++ or Ada, for portability. RTFiles must support compilers without C++ support.

Another issue is overhead and reliability. C++ exception handling needs a lot of run-time system support routines, which might add too much code to a small embedded system. C++ exceptions are objects dynamically allocated from the heap, but many embedded systems do not want to use any dynamic memory allocation to avoid heap fragmentation and out-of-heap-space problems. For example, what would happen if an RTFiles device driver throws a disk-write-protection exception, and the heap allocation called by throw throws an out-of-memory exception?

The solution to the problem is to implement a simple exception-handling library in C with the following goals:

  • No dynamic memory allocation.
  • Robust (the exception handling library itself must not fail).
  • Must support both exception-handlers and finally-handlers.
  • Reentrant for multitasking applications.

In this article, we describe how we designed and implemented our exception-handling library, demonstrate how it is used, and compare it to C++ exception handling. The complete source code is available through the DDJ Resource Center accessible to registered users of www.ddj.com.

   jmp_buf jumper;

   int SomeFunction(int a, int b)
   {
      if (b == 0) // can't divide by 0
         longjmp(jumper, -3);
      return a / b;
   }

   void main(void)
   {
      if (setjmp(jumper) == 0)
      {
         int Result = SomeFunction(7, 0);
         // continue working with Result
      }
      else
         printf("an error occured\n");
   }

Example 1: A simple error-handling approach based on setjmp() and longjmp().

Handling Exceptions in C

The basic function of exception handling is to transfer control to an exception-handler when an error occurs, where the handler resides somewhere higher up in the current function call hierarchy. Standard C has a mechanism to accomplish this: setjmp() and longjmp(). Example 1 shows a simple implementation of error handling based on setjmp()/longjmp(). However, this example is a little too simple. It relies on a single global variable called "jumper," which contains the information where the exception handler is. However, we need many different exception handlers in different functions, so how will SomeFunction() know which jumper to use? Another issue is multitasking. Each task will have its own call hierarchy and thus needs its own jumper or set of jumpers.

What we really need is a dynamically linked list of exception handler records. Such a record will contain a jmp_buf structure and supplemental information (for example, whether an exception has been raised, has it been handled, what is its error code, and so on). Each function that defines an exception-handler adds such a record to the list and removes it from the list when it returns. The exception-handler records are allocated in the function's stack frame since we do not want to use the heap.

To make the whole thing reentrant, a separate list root is maintained per task. Multitasking operating systems will usually provide some mechanism to maintain per-task values. For example, Win32 has Task Local Storage (TLS), and RTKernel-32, the real-time kernel component of On Time RTOS-32, has both  Win32 TLS and its own Task User Data. TLS allows an application to allocate a pointer value for every task (even for those that have not been created yet). At run time, this value can be set and retrieved by the current task. Our exception handling library uses TLS to store the exception-handler list root pointers. If used in a single-task environment, the list root can simply be implemented as a single global variable.

   #define DIVIDE_BY_ZERO -3
   int SomeFunction(int a, int b)
   {
      if (b == 0) // can't divide by 0
         XRaise(DIVIDE_BY_ZERO);
      return a / b;
   }

   void main(void)
   {
      XRecord XData;
      XLinkExceptionRecord(&XData);
      switch (setjmp(XData.Context))
      {
         case 0: // this is the code block
            {
               int Result = SomeFunction(7, 0);
               // continue working with Result
            }
            break;
         case DIVIDE_BY_ZERO:
            printf("a division by zero occurred\n");
            break;
         default:
            printf("some other error occurred\n");
            break;
         case XFINALLY:
            printf("cleaning up\n");
      }
      XUnLinkExceptionRecord(&XData);
   }

Example 2: setjmp()/longjmp()-based error handling with exception records allocated on the stack.

   void main(void)
   {
      XTRY
         case XCODE: // this is the code block
            {
               int Result = SomeFunction(7, 0);
               // continue working with Result
            }
            break;
         case DIVIDE_BY_ZERO: // handler for a
                                 specific exception
            printf("a division by zero occurred\n");
            break;
         default: // default handler
            printf("some other error occurred\n");
            break;
         case XFINALLY: // finally handler
            printf("cleaning up\n");
      XEND
   }

Example 3: This program is functionally identical to Example 2, but the details of setting up the exception handler block have been moved to macros XTRY and XEND.

Example 2 shows an improved version using exception handler records on the stack. XLinkExceptionRecord() puts the given exception-handler record onto the current task's exception-handler list and initializes it. XUnLinkExceptionRecord() removes the exception-handler record from the list. XRaise() throws an exception, which is a negative error code in RTFiles. XRaise() retrieves the top-level exception-handler record of the current task and then calls longjmp() with the given error code. This transfers control to the correct handler.

To simplify the syntax, the exception-handler library's header file defines a few macros to encapsulate building and destroying an exception-handler block. Example 3 shows the same program using these macros.

Semantic Details

One big advantage of a home-grown exception handling library is that we can define the semantics to best fit the needs of our application. For example, the most common case in RTFiles is that a function cannot handle an error, but some cleanup action is required. Typically, some global data structures of the filesystem are protected on entry to the filesystem using semaphores. We must ensure that such semaphores are released no matter how the function is left (through a normal return statement or through an exception). We thus reserved a special exception code (-1, defined as XFINALLY), which shall always be raised exactly once when an XTRY block is left. Such a finally-handler is not supported by C++ exception handling.

Another difference from C++ exceptions is that executing an exception-handler does not automatically complete handling of the exception. If the handler does not explicitly call function XHandled(), the exception-handling library will continue to pass the exception to handlers higher up in the list (called "exception propagation" or "stack unwinding"). We decided on these semantics because they reflect the typical case in RTFiles. However, in other applications, this could be handled differently, such as by using C++ semantics where an exception is considered handled once an exception handler has been invoked.

The RTFiles API consists of C functions that all return integer values. Positive values indicate success and negative values indicate errors. For example, the function to open a file, RTFOpen(), will either return a valid, positive file handle or an error code such as RTF_FILE_NOT_FOUND, which is defined as -9. To keep things simple, we use the standard RTFiles error codes as exception values. This lets us automate returning correct error codes to the application. Because all RTFiles API functions contain a top level XTRY block, when leaving the block, we can simply return the current exception value. Basically, we propagate exceptions out of RTFiles by simply returning the exception value. This is implemented by the macro XEND, which returns the error code if it finds that the last exception-handler record has been removed and that a still unhandled exception is being propagated.

To implement these semantics, the exception-handling library must know the current state of processing, stored in the current top-level exception-handling record. Three states are distinguished:

  • XCode. The code body of the try block is being executed.
  • XHandling. An exception-handler is being executed.
  • XFinally. The finally block is being executed.

When an XTRY block is entered, the initial state is set to XCode by function XLinkExceptionRecord(). If the code section completes without raising any exceptions, XUnLinkExceptionRecord() is called next, which will then set the state to XFinally and execute the finally-handler. If, however, an exception is raised, XRaise() sets the state to XHandling and calls the appropriate handler. If the handler does not call XHandled() to indicate that the exception is now handled, XUnLinkExceptionRecord() executes the finally-handler and moves on to the next handler in the handler chain. This means that any code following the XTRY block will never get executed, and the current execution frame is abandoned.

Of course, this process has to stop somehow. If an exception-handler can handle the error, it will call XHandled() and normal execution will continue after the current XTRY block. If the outermost exception-handler has not handled the exception, we just pass the exception code (which is an RTFiles error code) back to the application. However, most applications will propably prefer to abort the program with a fatal error (just like C++, for example).

As long as the OS-dependent functions for TLS data are defined (in our example, we just use a single global variable), the exception library can be compiled with any ANSI C compiler. However, our goal to become independent of the C++ run-time systems has not been reached yet. Looking at the source code of typical implementations of longjmp() reveals that longjmp() references a lot of the C++ exception-handling support routines. This makes sense because C++ must ensure that all objects with a local scope are destroyed when the respective scope is left. As longjmp() can leave a local scope, it must also call the destructors of all objects going out of scope. Because we only use C in RTFiles, this functionality is not required, and we do not want longjmp() to pull in so much code we would never need. Thus, we implemented our own versions of setjmp() and longjmp(); see Listing One. The two functions, XSaveContext() and XRestoreContext(), only have to save/restore the stack frame registers, the return address, and any registers the compiler might use for register variables. By defining symbol XWIN32, our alternate functions are used by the exception-handling library instead of setjmp()/longjmp().

   XTRY
      case XCODE
         // code body
         break;
      [case ERROR_1:
         // handler for ERROR_1
         break;
         // more handlers go here
      ...]
      default:
         // handle all other errors
         break;
      case XFINALLY:
         // finally handler
   XEND or XENDX

Example 4: The basic structure of an XTRY block.

Using the C Exception Handling Library

XTRY blocks can be nested to any depth, either within a single function or across function calls. The XTRY block has a code body, any number of exception-handlers to handle specific exceptions, a single default handler to catch all other exceptions, and a finally-handler. Example 4 shows the basic structure of of an XTRY block.

The XTRY block is closed with macro XENDX or XEND. XENDX is used in functions that cannot return the error code as a return value. This is frequently the case for functions internal to RTFiles, declared as static void. If XENDX finds no outer exception-handler, it reports a fatal error. Basically, this means that a function containing an XENDX block may only be called while an XTRY block already resides on the call stack. This is the case in RTFiles, since all API functions (the only entry points to RTFiles) have XTRY blocks.

Within an XTRY block, a few exception-management functions are available. XRaise(int XValue) raises an exception with the given error code. XValue must be a value less than or equal -2, because positive values are not considered errors and -1 is reserved for the finally-handlers. XHandled() stops propagating an exception. It may only be called from an exception-handler. XReExecute() can be called by an exception-handler to execute the code body of the current XTRY block again. For example, this can be used for retries. Finally, macro XVALUE returns the value of the exception currently being processed.

There are also a few restrictions that must be observed. Due to the implementation of setjmp() and longjmp(), all local variables used within more than one part of an XTRY block (code body, exception handler, finally handler, or outside the XTRY block) must be declared as volatile. This is required by the C ANSI standard, which is explained in more detail in "Register Variables and longjmp()". Additionally, this C exception-handling library should not be mixed with C++ code within the same source file, because it could cause destructors not to be called.

How Does it Compare to C++ Exceptions?

Although C++ was never an option for RTFiles, we do want to check that our goals have been met. In particular, our C exception-handling library should have little run-time overhead, and it should need less code space than C++ exceptions. Listings Two and Three show a C and a C++ version of a benchmark program, respectively. One thing difficult to compare is the finally-handler, which is not supported by C++. In the benchmark, the finally-handler should merely increment an integer. In the C++ version, this statement has been placed in the destructor of a local class object. C++ guarantees that such a destructor is called when its object goes out of scope, regardless of the method to leave the scope.

ProgramCode SizeTime (no throws)Time (with throws)
XBench.c 4.6k 1392 ms 1362 ms
CPPBench.cpp 35.3k 1492 ms 71343 ms

Table 1: Code sizes and benchmark results for C and C++ exception-handling compiled with Borland C++ Builder 4.0, run under Windows NT.

Table 1 lists the execution times and code sizes of both programs. Execution times are given for the case where no exceptions are thrown and for one throw per iteration. The C program has been linked with a stripped-down run-time system with C++ exception-handling support removed while the C++ version is linked with an unmodified run-time system. The tests were performed with Borland C++ Builder 4.0 for Win32 under Windows NT. While there is only a small difference in execution times when no exceptions occur, the C exception library easily outperforms C++ in all other disciplines.

Conclusion

This simple exception-handling library has been a great help in implementing RTFiles. The mechanism is easy to use, portable, uses no dynamic memory allocation, and is efficient. When used correctly (that is, when you do not call XRaise() while no XTRY block is present on the call stack), it cannot fail. In particular for embedded systems, where low resources preclude the use of C++, using the C exception-handling library can radically reduce the complexity of error handling.


Care must be taken with local variables in functions using setjmp(). In Example 5, for instance, you would expect the following program output:

   1: 1
   2: 2
   3: 3

However, with most optimizing compilers, it will be:

   1: 1
   2: 2
   3: 2
   void main(void)
   {
      jmp_buf jumper;
      int LocalVar = 1;

      printf("1: %i\n", LocalVar);
      if (setjmp(jumper) == 0)
      {
         LocalVar++;
         printf("2: %i\n", LocalVar);
         longjmp(jumper, 1);
      }
      LocalVar++;
      printf("3: %i\n", LocalVar);
   }

Example 5: Sample Program.

In particular, the incorrect result will be produced whenever the compiler chooses to allocate LocalVar as a register variable. setjmp() will save all registers used for register variables in the given jmp_buf. Even though the register variable has been incremented after the call to setjmp(), longjmp() will restore the value it had at the time of setjmp(). The only way to prevent this problem is to declare such variables as volatile. This is even required by the ANSI C standard in section 7.6.2.1.

Since the C exception-handling library uses setjmp() in its XTRY macro and calls longjmp() in macro XEND and XRaise(), all parameters and local variables of functions with XTRY blocks must be declared as volatile if their values must be preserved across the XTRY and XEND macros. This is even the case when alternate functions for context saving/restoring are used instead of setjmp()/longjmp(), since they also can only restore register variable values in effect when the context was saved.


Listing One:

   ; RTFEX32.ASM
   ; Copyright (c) 1998,99 On Time Informatik
   ; http://www.on-time.com/
   ; Custom context save/restore functions for C Exception Handling Library 
   ; This is what we want to implement:
   ; typedef struct {
   ;    unsigned long esi, edi, ebx, ret, ebp, esp;
   ; } XContext;
   ; int __stdcall XSaveContext(XContext * C);
   ; void __stdcall XRestoreContext(XContext * C, int Value); 

   .386

   XContext STRUC
      _esi DD ?
      _edi DD ?
      _ebx DD ?
      _ret DD ?
      _ebp DD ?
      _esp DD ?
   XContext ENDS

   _TEXT SEGMENT DWORD USE32 PUBLIC 'CODE'

   ASSUME CS:_TEXT

   PUBLIC XSaveContext
   PUBLIC _XSaveContext@4
   PUBLIC XRestoreContext
   PUBLIC _XRestoreContext@8

   XSaveContext proc near
   _XSaveContext@4 label near
      pop ecx                           ; ret address
      pop edx                           ; parameter C
      mov [edx+_esi], esi               ; save all required registers
      mov [edx+_edi], edi
      mov [edx+_ebx], ebx
      mov [edx+_ret], ecx
      mov [edx+_ebp], ebp
      mov [edx+_esp], esp
      xor eax, eax                      ; return code is zero
      jmp ecx                           ; and return
   XSaveContext endp

   XRestoreContext proc near
   _XRestoreContext@8 label near
      mov edx, [esp+4]                  ; parameter C
      mov eax, [esp+8]                  ; parameter Value, set as return value
      mov esi, [edx+_esi]               ; restore all required registers
      mov edi, [edx+_edi]
      mov ebx, [edx+_ebx]
      mov ebp, [edx+_ebp]
      mov esp, [edx+_esp]
      jmp [edx+_ret]                    ; and jump to saved context
   XRestoreContext endp

   _TEXT ENDS

   END

Listing Two:

   // XBENCH.C
   // Copyright (c) 1998,99 On Time
   // http://www.on-time.com/
   // Benchmark for the C Exception Handling Library

   #include <windows.h>
   #include <stdlib.h>
   #include <stdio.h>

   #include <rtfex.h>

   #define DIVIDE_BY_ZERO -3  // an error code

   // some global variables to count
   // iterations, exceptions, and cleanups

   int Calls, Exceptions, Finallys;

   ///////////////////////////////////////
   int SomeFunction(int a, int b)
   // functiom which can raise an exception
   {
      if (b == 0)
         XRaise(DIVIDE_BY_ZERO);
      return a / b;
   }

   ///////////////////////////////////////
   int TestFunction(int a, int b)
   // test function containing an XTRY block
   {
      int volatile Result;
      Calls++;
      XTRY
         case XCODE:
            Result = SomeFunction(a, b);
            break;
         case DIVIDE_BY_ZERO:
            Exceptions++;
            XHandled();
            break;
         default:
            printf("unknown exception!\n");
            break;
         case XFINALLY:
            Finallys++;
            break;
      XENDX
      return Result;
   }

   ///////////////////////////////////////
   void Bench(int a, int b, int Iterations)
   // benchmark function to call TestFunction in a loop
   // and print timing and statistics 
   {
      DWORD T0, T1;
      int i;

      Calls = Exceptions = Finallys = 0;
      T0 = GetTickCount();
      for (i=0; i<Iterations; i++)
         TestFunction(a, b);
      T1 = GetTickCount();
      printf("%10i %10i %10i %10u\n", Calls, Exceptions, Finallys, T1-T0);
   }

   ///////////////////////////////////////
   int main(void)
   {
      printf("Interation Exceptions Finallys Milliseconds\n"
      "------------------------------------------------\n");
      Bench(1, 1, 1000000); // no exceptions
      Bench(1, 0, 1000000); // raise one exception per loop
      return 0;
   }

Listing Three:

   // CPPBENCH.CPP
   // Copyright (c) 1998,99 On Time
   // http://www.on-time.com/
   // Benchmark for C++ Exception Handling

   #include <windows.h>
   #include <stdlib.h>
   #include <stdio.h>

   #define DIVIDE_BY_ZERO -3 // an error code

   // some global variables to count
   // iterations, exceptions, and cleanups

   int Calls, Exceptions, Finallys;

   ///////////////////////////////////////
   int SomeFunction(int a, int b)
   // functiom which can raise an exception
   {
      if (b == 0)
         throw DIVIDE_BY_ZERO;
      return a / b;
   }

   ///////////////////////////////////////
   int TestFunction(int a, int b)
   // test function containing try block
   {
      int Result;

      class Finally {
      public:
         Finally() { Calls++; }
         ~Finally() { Finallys++; }
      } FinallyHandler;

      try
      {
         Result = SomeFunction(a, b);
      }
      catch (int& ErrorCode)
      {
         switch (ErrorCode)
         {
            case DIVIDE_BY_ZERO:
            Exceptions++;
            break;
         default:
            printf("unknown exception!\n");
            throw;
         }
      }
      catch (...)
      {
         printf("non integer exception!\n");
         throw;
      }
      return Result;
   }

   ///////////////////////////////////////
   void Bench(int a, int b, int Iterations)
   // benchmark function to call TestFunction in a loop
   // and print timing and statistics 
   {
      DWORD T0, T1;
      int i;

      Calls = Exceptions = Finallys = 0;
      T0 = GetTickCount();
      for (i=0; i<Iterations; i++)
         TestFunction(a, b);
      T1 = GetTickCount();
      printf("%10i %10i %10i %10u\n", Calls, Exceptions, Finallys, T1-T0);
   }

   ///////////////////////////////////////
   int main(void)
   {
      printf("Interation Exceptions Finallys Milliseconds\n"
      "------------------------------------------------\n");
      Bench(1, 1, 1000000); // no exceptions
      Bench(1, 0, 1000000); // raise one exception per loop
      return 0;
   }

Which is used to throw a exception?

All methods use the throw statement to throw an exception. The throw statement requires a single argument: a throwable object. Throwable objects are instances of any subclass of the Throwable class. Here's an example of a throw statement.

Can you throw exceptions in C?

The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.

Which is used to handle the exception in C?

Explanation: Exception handler is used to handle the exceptions in c++.

What is the use of throw exception in C#?

Exceptions are used to indicate that an error has occurred while running the program. Exception objects that describe an error are created and then thrown with the throw keyword. The runtime then searches for the most compatible exception handler.