CS360 Lecture notes -- Error Checking

  • Jian Huang
  • CS360
  • Directory: ~huangj/cs360/notes
  • Lecture notes: http://www.cs.utk.edu/~huangj/cs360/360/notes/errorchecking.html
    This is one of the preliminary lectures in the course.

    Error Checking

    One of the major differences between systems programming and application programming is that error checking is not something nice to have but so essential that one cannot live without. Very commonly, you can see people do "printf" to output error messages to the console. Although better than not having any error checking, however, this is not enough even when not doing systems programming.

    When each program runs and becomes a process, there are three files opened for it by default, stdin, stdout and stderr. The first two obviously are the input and output consoles and the third, stderr, is where the error messages are supposed to go to. stdin and stdout may be redirected, while stderr cannot. Therefore the error messages are best output using:

         fprintf(stderr,"your error message");
    
    It should also be known to you that printf and fprintf(stdout,"...") do the same task.

    Besides using fprintf, there are two other important tools for use in error checking: perror and assert, both are ANSI C standard and available on all operating systems that claim to support ANSI C'1987.


    perror

    When a computer is turned on, the program that gets executed first is called the ``operating system.'' It controls pretty much all activity in the computer. This includes who logs in, how disks are used, how memory is used, how the CPU is used, and how you talk with other computers. The operating system we use is called "Unix".

    The way that programs talk to the operating system is via ``system calls.'' A system call looks like a procedure call (see below), but it's different -- it is a request to the operating system to perform some activity.

    System calls are expensive. While a procedure call can usually be performed in a few machine instructions, a system call requires the computer to save its state, let the operating system take control of the CPU, have the operating system perform some function, have the operating system save its state, and then have the operating system give control of the CPU back to you. This concept is important, and will be seen time and time again in this class.

    Usually when an error occurs in a system or library call, a special return value comes back, and a global variable "errno" is set to say what the error is. For example, suppose you try to open a file that does not exist:

    #include < stdio.h >
    #include < errno.h >
    
    main()
    {
      int i;
      FILE *f;
    
      f = fopen("~huangj/nonexist", "r");
      if (f == NULL) {
        printf("f = null.  errno = %d\n", errno);
        perror("f1");
      }
    }
    
    ch1a.c tries to open the file ~huangj/nonexist for reading. That file doesn't exist. Thus, fopen returns NULL (read the man page for fopen), and sets errno to flag the error. When you run the program, you'll see that errno was set to 2. To see what that means, you can do one of two things:

    This is the standard interface for errors. Learn to use perror.

    assert

    Most of the time, there is a need to make assumptions when you write code. Everybody does it. But what if the assumption is wrong? Is there a good way to check it? The answer is to use assert.

    The most typical use of the assert (very likely implemented as a macro on most operating systems you can find) is to identify program errors during development. The argument given to assert should be chosen so that it holds true only if the program is operating as intended. The macro evaluates the assert argument and, if the argument expression is false (0), alerts the user and halts program execution. No action is taken if the argument is true (nonzero).

    When an assertion fails, an output message with the following text is generated:

    assertion failed in file name in line num
    
    where name is the name of the source file and num is the line number of the assertion that failed.

    The liberal use of assertions throughout your programs can catch errors during development. A good rule of thumb is that you should write assertions for any assumptions you make. For example, if you assume that an argument is not NULL, use an assertion statement to check for that condition.

    void checkerror_strcpy(char * src, char *dst)
    {
       assert(src!=dst);
       assert(src!=NULL);
       assert(dst!=NULL);
    }
    
    Here we check that some assumptions we made for strcpy are true. After we are sure there are no errors in the software, we can easily disable all assertion checks adding "#define NDEBUG" before where "#include < assert.h >" appears in the source code.


    Summary

    To write code that may be useful to other people, error checking takes you one important small step in the right direction. For your future career, and as an immediate payback in saving your own time when you do the lab assignments for CS360, I strongly suggest that each of you add error checking very liberally to all codes you write.