How to Trap Integer Divide-By-Zero Exceptions in C

ID: Q73153


The information in this article applies to:


SUMMARY

In Microsoft C versions 5.1 and later, when performing integer math it is possible to generate an exception by attempting to divide an integer by zero. Since the result of a division by zero operation is undefined, Intel sets aside an interrupt (Interrupt 0) for the purpose of indicating that a divide-by-zero exception has occurred. In the default configuration, the run-time library shipped with Microsoft language products will issue the following error and terminate the program:


   run-time error R6003
   - integer divide by 0 
Depending on your program's requirements, this may be undesirable behavior. If you want to trap this error and handle it in a more appropriate manner, you need to set up an exception handler for Interrupt 0. This can be done using the _dos_setvect() run-time function for MS-DOS, or the DosSetVec() API function for OS/2.


MORE INFORMATION

The code example below demonstrates how this is done in Microsoft C or Microsoft QuickC. The run time will install a default Interrupt 0 handler during execution of the startup code (see CRT0DAT.ASM for specifics). If you want to install a different handler, you should save the original interrupt vector so that you can restore it as part of the cleanup procedure. With the run-time library in MS-DOS, this is done using the _dos_getvect() function. Using the OS/2 API function DosSetVec(), one of the arguments you pass is the address of where you want the original function pointer stored.

The actual function that handles the error must be declared with the _interrupt keyword. Doing so causes the compiler to save all the registers on function entry, restore them when the function terminates, and issue an "iret" instruction to return to the calling procedure. For more information on the _interrupt keyword, see the online help.

Inside the exception handler, you have a couple of choices on how to handle the error: you can either terminate the process or ignore the error and continue. If you decide to terminate the process, remember to call one of the run-time exit routines to shut down the run time [exit(), _exit(), _cexit(), or _c_exit()]. If you decide to ignore the error and continue, you must increment CS:IP to skip over the instruction that caused the error. This is also illustrated below in the sample program.

Sample Code


/* Compile options needed for DOS : /G2 /DDOS
   Compile options needed for OS/2: /G2 /DOS2
*/ 

#ifdef OS2
#define INCL_DOSMISC
#include <os2.h>
#endif

#pragma check_stack( off )
#pragma check_pointer( off )

#include <dos.h>
#include <stdio.h>

void main(void);
void _far _cdecl _interrupt ZeroDivTrap(unsigned, unsigned, unsigned,
                                        unsigned, unsigned, unsigned,
                                        unsigned, unsigned, unsigned,
                                        unsigned, unsigned, unsigned,
                                        unsigned);
int arg1 = 10;
int arg2 = 0;
int arg3;
int rc;        // return codes...

void main(void)
{
#ifdef DOS

   // For MS-DOS, first retrieve the old handler,
   // then set up the new one.

   void (_interrupt _far *OldZeroDiv)();

   OldZeroDiv = _dos_getvect((unsigned)0x00);

   _dos_setvect(0x00, ZeroDivTrap);

#endif

#ifdef OS2
   // For OS/2, the API will take care of both for us.

   PFN OldZeroDiv;
   PFN NewZeroDiv;
   rc = DosSetVec(VECTOR_DIVIDE_BY_ZERO,
                  (PFN)ZeroDivTrap, &OldZeroDiv);

   if (rc != 0)
      printf("rc: %d - DosSetvec() Failed...\n", rc);

#endif

   // Now let's try to generate an error. This should do it:
   arg3 = arg1 / arg2;

   // Just to make sure we are back where we started, print a message.

   printf("Back in the main code...\n");

   // Clean up after ourselves.

#ifdef DOS
   _dos_setvect(0x00, OldZeroDiv);
#endif

#ifdef OS2
   rc = DosSetVec(VECTOR_DIVIDE_BY_ZERO, OldZeroDiv, &NewZeroDiv);

   if (rc != 0)
      printf("rc: %d - DosSetvec() Failed...\n", rc);
#endif

   // All done!
}

void _far _cdecl _interrupt ZeroDivTrap(unsigned _es,
          unsigned _ds, unsigned _di, unsigned _si,
          unsigned _bp, unsigned _sp, unsigned _bx,
          unsigned _dx, unsigned _cx, unsigned _ax,
          unsigned _ip, unsigned _cs, unsigned _flags)
{
   char chAddrByte;

   printf("This is from inside the Exception Handler...\n");

   // If we want to terminate the program, do it now. Remember to
   // call one of the exit routines to shut down the run time
   // [for example, exit(), _exit(), _cexit(), or _c_exit()].

   // On the other hand, if you want to continue (knowing of course
   // that the results are incorrect for the divide operation), you
   // have to determine the size of the instruction that caused the
   // exception and jump past it. This is done by examining the mod
   // and r/m bits in the second byte of the op code and incrementing
   // the IP register by the required amount. See the MASM or Intel
   // docs for more information.

   chAddrByte = *(char far *)(((unsigned long)_cs << 16) + _ip + 1);

   chAddrByte &= 0xC7;           // Mask off unneeded bits.

   if (chAddrByte == 6)          // If mod = 0 and r/m = 6, then
      _ip += 4;                  //      opcode is 4 bytes.
   else
      switch (chAddrByte >> 6)   // Else, we look at mod only.
      {
      case 0:
      case 3:
         _ip += 2;
         break;
      case 1:
         _ip += 3;
         break;
      case 2:
         _ip += 4;
         break;
      }
} 

Additional query words: kbinf 1.00 1.50 6.00 6.00a 6.00ax 7.00


Keywords          : kb16bitonly 
Version           : 
Platform          : 
Issue type        : 

Last Reviewed: August 8, 1999