What to do if your program doesn't run

  • Click here for Math408 home page
  • Click here for Prof. Sawyer's home page

    Table of contents:

    1. Look at your output!
    2. Run your compiler with warning messages and read them!
    3. Signals and math exceptions.

    1. Look at your output:

    If your output says

    The answer to problem #11.1739 is 2.

    then you have probably interchanged some variables in your program. Your program may also say something like

    The answer to problem #1807322238 is 4.77573e-314.

    This was the result of having an integer (4 bytes in this case) and a double precision floating-point variable (8 bytes) in the wrong order as arguments to a printf function (see below). The program treated the first four bytes of the 8-byte floating-point as if it were an integer and then took the remaining eight bytes --- the last four bytes of the double plus the following integer --- as a floating-point number, with the resulting disfunctional display.

    Incidentally, the two lines aboves were output from the C program

    #include <stdio.h>

    int main(void)
     { double dval=11.1739; int nprob=2;
      printf ("The answer to problem #%g is %d.\n", dval, nprob);
      printf ("The answer to problem #%d is %g.\n", dval, nprob);
      return 0; }

    2. Always run your compiler with warning messages!

    If you were using the MinGW C compiler, and compiled the C program above as

    gcc   -o c_what1   c_what1.c

    and then ran c_what1, you would get the output above with no warning. However, if you had compiled the program using

    gcc   -Wall   -o c_what1   c_what1.c

    then you would get several warning messages about printf arguments not matching the variables in its format string. These would have been a clue to fix the printf statements. (Incidentally, the -W   in -Wall stands for Warning and all for All, so that -Wall is not a reference to a real wall.)

    A more insidious example is the C program

    #include <stdio.h>

    int main(void)
      { int x, xx=7;
      printf ("The variable xx was initialized as int xx=7;\n");
      printf ("However, my value of xx is %d\n",x);
      return 0; }

    which had output

    The variable xx was initialized as int xx=7;
    However, my value of xx is 2

    The reason for this curious output is that `xx' is initialized at 7 (so that xx=7 until it is changed), but the variable in the second printf statement is x, not xx. In this case, x was declared but not initialized, and that location in memory happened to have the value 2 when the program was run. This sort of error is hard to detect and can occasionally cause believable but highly incorrect results.

    However, if you had compiled this program as

    gcc   -Wall   -o c_what2   c_what2.c

    then you would have gotten a warning message that `xx' was given a value but that value was never used. The latter suggests that you made an error of exactly this sort.

    Curiously, the Microsoft Visual C command-line compiler misses that particular warning message, but does say that the variable `x' was used (in the second printf statement) without being initialized (which gcc should also have said). However, either warning message should be taken as a clue that something potentially serious is wrong.

    These two examples show why fussy compiler warning messages can be very helpful if you pay attention to them. Again, if the program was compiled as

    gcc   -o c_what2   c_what2.c

    there would have been absolutely no warning or error messages.

    3. Signals and math exceptions.

    Another type of error is illustrated in the C program

    #include <stdio.h>

    int main(void)
      { char *str="Hello"; int xx=17;
      printf ("My value of str is %s\n",str);
      printf ("My value of xx is %s\n", xx);
      return 0; }

    In C, text strings are handled as pointers to arrays of variables of type char (usually one byte each) that end with a zero byte. In this case, the compiler creates an array of 6 bytes that has Hello as its first 5 characters and then 0 to end the text string. The program then stores the address of this array in the variable str.

    When the printf function sees %s in its format string and str in its argument list, it fetches the characters at that address and adds them to the printed output. The output from this line will be

    My value of str is Hello

    The %s in the second printf statement also tells the computer to look for an array of chars for text, but tells it to look at the literal address xx=17 in your computer instead of at the address of a well-defined string.

    Modern computers keep a list of valid pseudo (or pretend) address ranges that are mapped into various places. These places are usually ranges of memory, but can also be parts of files and occasionally parts of external devices. If xx=17 is not part of one of these address ranges, the computer will have no idea how to interpret this command. For an address value as small as xx=17, that is the most likely case.

    Alternatively, the address may exist (that is, be mapped somewhere), but may be in sensitive system memory or even in an important part of someone else's program.

    In either case, the computer will crash with what is called a

    SEGMENTATION FAULT

    By definition, a segmentation fault is an attempt to read from or write to memory that you do not have permission to read from or write to, or which does not exist at all because it is not in a defined memory segment.

    Earlier versions of Microsoft Windows handled segmentation faults in a more-or-less reasonable way. The program immediately exited so that you could fix the problem. Print statements before the segmentation fault may or may not be printed, because the program may have been told to exit before clearing out its print buffer.

    Unfortunately, the behavior in Windows XP is much more annoying. The computer first freezes for 4 or 5 seconds, as if cringing in embarrassment. A window then drops down telling you that Microsoft is extremely sorry that your program has crashed, and would you like to send an email to Microsoft with your specific complaint. You can be sure that the name of your program will be part of this error message, and if the name is not familiar to Microsoft, then the email will be ignored. This apology window can take 3 or 4 mouse clicks to get rid of before you can begin to fix your program.

    Crashing program bugs:

    The easiest way to correct the annoying behavior described above is to include a call to the function ``set_newsignals()'' at or near the top of your program. (This may also be called ``set_errsignals()''.) The function ``set_newsignals()'' is in an accompanying C library ``statlib.c'' on the course Web site, so that if you compile your program along with ``statlib.c'' it will be available. The rest of the text below describes what this mysterious-looking function call does.

    In general, serious errors in a compiled program lead to two classes of interrupts, which are called SIGNALS and MATH EXCEPTIONS respectively. When serious program errors are UNHANDLED on PCs, then Microsoft takes over and you get these irritating drop-down windows. (Actually, ``serious'' program errors is not quite the right term: The most serious program errors are errors that give you plausible but incorrect results, because you might be misled into telling people information that is not correct. Any program error that causes an immediate program halt is potentially less serious.)

    Segmentation faults are the most common type of signal error. Another common type of signal errors are called ``floating-point exception'' (FPE) errors. FPE errors are usually divison by zero errors. MATH EXCEPTIONS result from commands like

    x=log(-5);    y=sqrt(-7);  or  z=exp(500000);

    The first two statements above are called range errors (for obvious reasons). The third is an overflow error, since exp(500000) is too large to store in a double-precision floating point number. The opposite condition

    w=exp(-500000);

    does not generally cause any problem: The computer normally replaces w by 0.0 and proceeds, although there may be problems later if you say 1/w. (A command like exp(-500000) is called an underflow, in contrast to exp(500000), which causes an overflow.)

    The line between math exceptions and signals is not completely defined and there is some variation among compiler writers. For example, the MinGW compiler treats 5/0 (division by zero for two integers) as an FPE signal, but (double)5/0 (division of a double by zero) as a math exception. The Microsoft Visual C compiler treats (double)5/0 as a math exception, but appears to treat (integer)5/0 as a nonstandard signal.

    Treatment of math exceptions:  By convention, modern C compilers handle math exceptions without program interruptions. The resulting floating-point numbers is replaced by what are called Infs (standarding for ``infinities'') or NaNs (standing for ``Not a Number''). Any further numerical operation on an Inf or NaN produces another Inf or NaN. When you try to display an Inf or NaN in a print statement, it shows up in computer output as something like #INF or #IND (the latter for Indefinite).

    Thus if your program calculates and displays a table of values, but one of the table calculations leads to a floating-point overflow or a logarithm or square root of a small negative number, then that table value would display as #INF or #IND but the rest of the table would display correctly. If you chose, you could rewrite the program to skip that case or to handle it differently, but the correct output for the remaining cases may be sufficient for what you were trying to find out.

    In contrast, some older C compilers do not handle math exceptions in this way, which could then lead to the irritating drop-down Microsoft windows.

    Incidentally, how math exceptions are handled in standard C89 ANSI or ISO C is programmable. You can make program calls that turn off some or all automatic math exception handling for a particular program, so that some or all math exceptions lead to immediate program crashes. Alternatively, there is a standard way of redirecting math exceptions that is similar to how signals can be redirected (see below). Any book about standard C will explain the details. Typically you can also learn about customizing math exceptions by reading comments in the include file  float.h  that must be part of any standard C installation.

    Redirecting signals:  The problem remains of how to handle serious signal errors such as segmentation faults. You can get rid of the irritating drop-down windows for these signals, or for most of them, by ``installing your own signal handler''. An example of how to do this is given in the following program.

    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>     /* Step I */

    void set_mysignals(void);     /* Step II */

    int main(void)
      { int i, ngals,nmiles;
      set_mysignals();     /* Step III - this calls a function below*/
      ngals=7; nmiles=0;
      printf ("The number of miles per gallon is %g\n", (double)ngals/nmiles);
      for (i=0; i<10; i++)
        printf ("%2s. Hello, World!\n", i+1);
      return 0; }
     
     
      /* Step IV is to include the following two functions: */
      /* The first function is the new `signal handler' */
      /* The second function tells the program to install (that is, use) the new `signal handler' */
     
    void my_signal_handler(int sig)
      { switch(sig) {
      case SIGSEGV:
          printf ("\nSEGMENTATION FAULT! (SIGSEGV signal)\n");
          break;
      case SIGFPE:
          printf ("\nINTEGER OR FLOATING POINT EXCEPTION! (SIGFPE signal)\n");
          break;
      case SIGILL:
          printf ("\nILLEGAL OPERATION EXCEPTION! (SIGILL signal)\n");
          break;
      default:
          printf ("\nUNKNOWN TRAPPED SIGNAL! Signal argument sig=%d\n",sig);
          break; }
      /* All cases in the switch statement come through here */
      printf ("\nPut enough printf statements in your program\n"
      " to see exactly where this happened!\n");
      /* Stop the program and return to the command line */
      exit(1); }
     
     
      /* This installs your new signal handler `my_signal_handler()' */
      /*   by redirecting 3 types of signals to `my_signal_handler()' */
      /* Note that the second argument of signal() is a pointer to a function. */
     
    void set_mysignals(void)
      { signal(SIGSEGV,my_signal_handler);
      signal(SIGFPE,my_signal_handler);
      signal(SIGILL,my_signal_handler); }

    The main() program above shows two apparent problems. The first print statement has a division-by-zero error in computing miles per gallon. However, in most modern C compilers, this generates a NaN instead of an unhandled exception. The program displays #INF for miles per gallon and proceeds. (The older Borland C Vs. 5.0 compiler generates an FPE signal error at this point.)

    The second problem is more serious: The following print statement tries to print an integer in front of `Hello, World!', but instead generates a segmentation fault by treating the integer as a string address. On PCs, this would lead to a Microsoft drop-down apology window unless you somehow changed how segmentation-fault signals are handled.

    This is done in the program in steps Steps I-IV as indicated in the comments. Step I includes a prototype of a standard C function (signal()) that installs signal handlers. Step II is a prototype for a function in this program (set_mysignals()) that uses signal(). Step III calls set_mysignals(), which should be done at the top of the main() function. Step IV is the code for (i) a new signal handling function (my_signal_handler()) and (ii) the function set_mysignals() that installs it.

    After you call set_mysignals() in the main() function, any segmentation fault generates a call to the function my_signal_handler() in your program with the function argument sig=SIGSEGV.  Here SIGSEGV is a constant that is defined in the include file signal.h. The switch statement in my_signal_handler() tells the program to jump to the `case SIGSEGV' label and then execute code until the break; statement. At that point, it exits the switch statement and takes the opportunity to nag you further in another print statement.

    The command exit(1); at the end of the signal handler is exactly equivalent to return 1; at the end of the main() function. In particular, the program exits gracefully with an error code of 1. A normal program exit (either at the end of the main() function or in an exit(0) call) indicates that the program has completed its tasks without any errors. Any exit() call guarantees that all print and file buffers are flushed and that all files are properly closed.

    What the program will do if you leave out the final exit(1); command in my_signal_handler() is likely to be compiler dependent, but it will probably be unpleasant.

    NOTE  If you include the file statlib.c on the Math408 Web site in your program, as well as the header filestatlib.h, then you can install essentially the same signal handler in your program by putting the single command

    set_newsignals();

    in your program, in place of Step III. (Note this is set_newsignals(), not set_mysignals().) Then leave out Steps I, II, and IV above. (That is, you can leave out the code for my_signal_handler() and set_mysignals().)  The necessary prototypes are in statlib.c and statlib.h, and the code for set_newsignals() and the signal handler itself are in statlib.c.

    
    

  • Top of this page
  • Click here for Math408 page
  • Click here to return to Stanley Sawyer's home page
  • Click here for the Mathematics Department home page

    Last modified April 30, 2007