Department of Engineering

IT Services

Debugging

Utilities and routines

Some compilers have flags to turn on extra checking. gcc for example has a -Wall option which gives a list of suspicious constructions as well as the usual compile errors.

There are also routines that are useful

  • When a system call fails it generally sets an external variable called errno to indicate the reason for failure. Using perror() (which takes a string as an argument) will print the string out and print the error message corresponding to the value of errno

  • assert() is useful for putting diagnostics into programs. When it is executed, if the expression it takes as an argument is false (zero), assert prints the expression's value and the location of the assert call. See the on-line manual page for more details.

If using these fail, try some of the following. If your machine's lacking any of these programs, look for public domain versions.

lint :-
is a program which gives the sort of warning messages about `unused variables' and `wrong number of arguments' that non-C compilers usually give. lint takes most of the same arguments as the compiler. It needs special libraries which are already provided.

cflow :-
To show which functions call which, use cflow. This produces an indented output which also gives an idea of function call nesting. An ansi-ized, much enhanced version is available by ftp from sunsite.unc.edu:/pub/linux/devel/C

cb :-
To standardise the indentation of your program, send it through cb, a C beautifier;
  cb ugly.c > lovely.c

cxrefs :-
tells you where variables and functions are mentioned. It's especially useful with multi-file sources.

adb :-
I only use adb to see why a core dump happened. If `myprog' causes a core dump then
   adb myprog
   $c
will show you what function's return addresses were on the stack when the crash happened, and what hex arguments they were called with. Quit using $q

Symbolic Debuggers :-
dbx, xdb, or gdb may be available to you. They are symbolic source-level debuggers under which you can run a program in trace mode allowing you to use breakpoints, query values, etc. To use this you will have to first compile your program with the -g flag.

cdecl :-
This program can help with C declarations. See man page for details. Some examples:-
unix: cdecl declare fptab as array of pointer to function returning int
int (*fptab[])()
unix:   cdecl explain int '(*fptab[])()' 
declare fptab as array of pointer to function returning int

cdecl is available from archives in comp.sources.unix/volume6.

array bounds :-
If you're using gcc there's a patch that lets you check whether you're going off the end of an array.

Some Common mistakes

Miscellaneous

  • A common mistake is to type `=' instead of `=='.
    if (i=3)
      return 1;
    else
      return 0;
    
    will always return 1 because the assignment `i=3' has the value 3 and 3 is true! gcc's warning option can alert you to this. You might also try to get into the habit of writing expressions like if (3==i) to safeguard yourself from this kind of error.

  • Comments in C can't be nested. Use the preprocessor directives to temporarily `comment out' blocks of code. Suppose you had the following code.
    if (i=6)
      z=mean(x,y); /* get the xy mean */ 
    mean(z,y);
    
    If you decided not to risk running mean you might do
    /* comment this fragment out
    if (i=6)
      z=mean(x,y); /* get the xy mean */ 
    mean(z,y);
    */
    
    but it wouldn't work because the first `/*' would be matched by the `*/' on the `mean(x,y)' line (the `/*' on that line being ignored), and `mean(z,y);' wouldn't be commented out at all. In this case the final `*/' would be flagged as an error, but you won't always be so lucky.

  • ...
    i = 3;
    j = 10;
    while (i<100);
      i = i+j;
    ...
    
    This while loop will go on for ever. The semicolon after the while condition is a null statement which forms the body of the loop so i will always be 3. Take away that semicolon and i = i+j becomes the body, which is probably what was intended.

  • When you have an if-else statement nested in another if statement, always put braces around the if-else. Thus, never write like this:
        if (foo)
          if (bar)
             win ();
        else
          lose ();
    
    (the else matches the closest if), always like this:
        if (foo)
          {
            if (bar)
                win ();
            else
                lose ();
          }
    

  • Don't be fooled by indentation. In the following fragment only the execution of the `j = 7;' statement is conditional upon the value of i.
    ...
    if (i==7)
      j = 7;
      k = 7;
    ...
    

  • The order of operations in an expression isn't guaranteed to be left-to-right. A line like
      a[i++] = b[i++];
    
    will have different results according to whether or not the i on the left-hand side is calculated before the right-hand side is evaluated.

  • The order of operator precedence sometimes surprises people.
     ...
     FILE *fp;
     ...
      if (fp=fopen(filename, "r") == NULL)
        return (NULL);
    
    Here the intention is to try opening a file, then compare the resulting fp to NULL to see if fopen failed. Unfortunately, what actually happens first is the test (fopen(filename, "r") == NULL) which has an integer result (non-zero if the statement is true). This result is then assigned to fp. The compiler should warn you about this problem. The code should have some extra brackets:-
     ...
     FILE *fp;
     ...
      if ((fp=fopen(filename, "r")) == NULL)
        return (NULL);
    

  • The following won't work as expected because the `~' character needs to be interpreted by the shell.
      if ((fp=fopen("~/data", "r")) == NULL)
        return (NULL);
    
    You'll have to find out your home directory (use getenv("HOME")) and append to it.

  • scanf takes pointers to the variables that are going to be set. The following fragment will cause a crash
    ...
    int i;
    scanf("%d",i); /* this should be scanf("%d",&i) */
    
  • The most uncomfortable bugs are those that seem to move as you hunt them down. Put in some printf() statements and they just disappear - or seem to. This could mean that you're writing off the end of an array or that one of your pointers has gone astray. You can protect against this by doing something like

    #define BUFLEN 10
    int x[BUFLEN], y;
    ...
    if (y >= BUFLEN || y<0)
      [error code here]
    else
      x[y] = 255;
    ...
    

  • There's a big difference between '\0' and "\0". Suppose you had

    char str[100];
    char *str_ptr;
         str_ptr = str;
    
    then str_ptr and str would both point to the first element in the array. Suppose you wanted to initialise this string by making the first element a zero byte. You could do
         strcpy(str_ptr, "\0") /* or strcpy(str_ptr, "") */
    
    or
         *str_ptr = '\0';
    
    but
         str_ptr = "\0";
    
    would do something quite different. It would create a string in your executable (namely "\0") and set str_ptr to point to it with potentially disastrous effects.

  • Turning on optimisation may change the behaviour of your program, especially if the program isn't perfect. For instance, if optimisation re-positions a variable into a register it's less likely to be 0 initially, so if you've not initialised variables before use you might get a surprize.

  • A function that returns a pointer either (1) takes a pointer as a parameter or (2) uses malloc to allocate memory to store the data in or (3) returns a pointer to a static buffer. As the user of a function, you must know which of the three it is in order to use the function; the manual page describing the function should give you this information.

declaration mismatch

  • getchar returns an integer, not a char as you might expect. If the integer value returned is stored into a character variable and then compared against the integer constant EOF, the comparison may never succeed, because sign-extension of a character on widening to integer is machine-dependent. Even if it does succeed, it may do so incorrectly. Read the manual page before using a function.

  • Suppose a function reverse takes a string. If the K&R C programmer accidentally writes
       reverse (str)
       {
        char *str;
        ...
       }
    
    rather than
       reverse (str)
        char *str;
       {
        ...
       }
    
    the compiler might not warn the programmer that the formal parameter str has the default type int and a local variable str is created which isn't initialised.

  • In the next example, it looks as if the programmer meant to define 2 pointers to integers. In fact, ptr2 is being defined as an integer.
    int*  ptr1, ptr2;
    

  • In K&R C the following code is legal but bogus; (ANSI C does automatic type conversion)
       int mean(num1, num2)
       int num1, num2;
       {
        ...
       }
    
       int i, answer;
       double f;
       /* deliberate mistake! */
       answer = mean(f,i);
       printf("The mean of %f and %d is %d\n", f, i, answer);
    

    C functions usually get given arguments via the stack. Calling functions put values on the stack then peel the same number of bytes off when returned to, so it doesn't matter to K&R C if the subfunction doesn't use or declare all the arguments that it is given. It doesn't even matter if it declares more arguments than given by the calling function as long as it doesn't write to these values. Were it to do so, it might well overwrite the address that the called function should return to. Such a problem might not be recognised for quite a while, and isn't easy to track down. This is where `lint' (see 16.1) becomes useful

  • If in one source file you have int array[100] and you want to use this array from another source file, you mustn't declare as extern int *array but as extern int array[]. An explanation of why this is so comes from Chris Volpe (volpecr@crd.ge.com)

    When you declare int array[100]; the compiler sets aside storage for 100 ints, at say, address 500. The compiler knows that array is an array, and when it tries to generate code for an expression like array[3], it does the following: It takes the starting address of the array (500), and adds to that an offset equal to the index (3) times the size of an int (typically 4) to get an address of 500+3*4=512. It looks at the int stored at address 512 and there's the int.

    When you give an external declaration in another file like extern int *array;, the compiler takes your word for it that array is a pointer. The linker resolves the symbol for you as an object that resides at address 500. But since you lied to the compiler, the compiler thinks there's a pointer variable stored at address 500. So now, when the compiler sees an expression like array[3], it generates code for it like this: It takes the address of the pointer (500) and, assuming there's a pointer there, reads the value of the pointer stored there. The pointer will typically reside at address 500 through 503. What's actually in there is indeterminate. There could be a garbage value stored there, say 1687. When executed the code gets this value, 1687, as the address of the first int to which the pointer points. It then adds the scaled index offset (12) to this value, to get 1699, and tries to read the integer stored at address 1699, which will likely result in a bus error or segmentation violation.

    The thing to remember about all this is that even though array[index] and pointer[index] can be used interchangeably in your source code, the compiler generates very different object code depending on whether you are indexing off an array identifier or a pointer identifier.

malloc

"Shipping C code has, on average, one bug per 55 lines of code. About half of these bugs are related to memory allocation and deallocation." - (anonymous but believable). malloc() allocates memory dynamically. The standard malloc() and free() functions need to be efficient and can't check the integrity of the heap on every call. Therefore, if the heap gets corrupted, seemingly random behaviour can occur. The following code won't work.

          char *answer;
          printf("Type something:\n");
          gets(answer);
          printf("You typed \"%s\"\n", answer);
answer, which is handed to the gets function as the location into which the response should be stored, has not been set to point to any valid storage. That is, we cannot say where the pointer answer points. Since local variables are not initialized, and typically contain garbage, it is not even guaranteed that answer starts out as a null pointer.

The simplest way to correct the question-asking program is to use a local array, instead of a pointer, and let the compiler worry about allocation:

          #include <string.h>

          char answer[100], *p;
          main(){
            printf("Type something:\n");
            fgets(answer, 100, stdin);
            if((p = strchr(answer, '\n')) != NULL)
                  *p = '\0';
            printf("You typed \"%s\"\n", answer);
          }

Note that this example also uses fgets instead of gets (always a good idea), so that the size of the array can be specified and fgets will not overwrite the end of the array if the user types an overly-long line, though unfortunately for this example, fgets does not automatically delete the trailing \n, as gets would.

Alignment problems can arise if malloc is used carelessly. Processors have different rules about (for instance) whether a long can be stored starting at an odd memory location. If you try to break these rules, your program will crash giving little or no clue why. The HP RISC chips only permit a double to start at an address divisible by 8, so trying something like

char *block = (char*) malloc(sizeof(double));
double d = 1.2;
     * (double*)block = d;

is likely to crash.

Find the bug

What looks wrong with these programs?
  • #include <stdio.h>
    #include <stdlib.h>
    main()
    {
    int i;
    for (i=0; i<10; i=i+1);
      printf("i is %d\n",i);
    }
    
  • #include <stdio.h>
    #include <stdlib.h>
    main()
    {
    int numbers[10];
    int i;
      for (i=1;i<=10;i++)
         numbers[i]=i;
      for (i=1;i<=10;i++)
         printf("numbers[%d]=%d\n", i, numbers[i]);
    }
    

  • #include <stdio.h>
    #include <stdlib.h>
    main()
    {
    int i;
    for (i=0; i<10; i=i+1)
      if (i=2)
        printf("i is 2\n");
      else
        printf("i is not 2\n");
    }
    
  • #include <stdio.h>
    #include <stdlib.h>
    main()
    {
    int i;
    for (i=0; i<10; i=i+1)
      if (i<2)
         printf("%d is less than 2\n",i);
         printf("and %d is not equal to, 2 either\n",i);
    }
    
  • #include <stdio.h>
    #include <stdlib.h>
    
    main()
    {
    int i;
     i = 0;
     while (i < 10);
       i = i + 1;
     printf("Finished. i = %d\n",i);
    }
    
  • #include <stdio.h>
    #include <stdlib.h>
    main()
    {
    int i;
    for (i=0; i<10; i=i+1)
        switch(i){
    
        case 0: printf("i is 0\n");
        case 1: printf("i is 1\n");
        default: printf("i is more than 1\n"); 
        }
    }
    
  • #include <stdio.h>
    #include <stdlib.h>
    main()
    {
    int i;
      for (i=0; i<10; i=i+1)
        /* check the value of i*/
        switch(i){
          /* is i 0?    
          case 0: printf("i is 0\n");
                  break;
          /* is i 1?
          case 1: printf("i is 1\n");
                  break;
          /* now the default case */
          default: printf("i is more than 1\n"); 
        }
    }
    
  • #include <stdio.h>
    #include <stdlib.h>
    main()
    {
    int i;
      i=3;
      i=i+2*i++;
      printf("i is now %d\n",i);
    }
    

  • #include <stdio.h>
    
    int main()
    {
    int a,b,c;
    int *pointer;
    
       c = 3;
       pointer = &c;
    
       /* divide c by itself */   
       a = c/*pointer;
       b = c /* set b to 3 */;
    
       printf("a=%d, b=%d\n", a,b);
    }
    

  • the following code works on some machines but crashes on others ...
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct {
       double *par;
       double *pos;
       double *vel;
     } ajoint;
    
    main()
    {
      ajoint *joint;
      joint = (ajoint *) malloc(sizeof(ajoint) + sizeof(double));
      joint->pos = (double*) (joint +1);
      *(joint->pos) = 0;
    }