L4:  Detecting and Correcting Errors

C detects few errors at compile time.  It is up to the programmer to produce test cases which reveal errors, and also up to the programmer to determine that in fact an error has occurred.  Both are difficult.  Producing test cases is tricky – to do so you may need to experiment, writing programs with small modifications which explore some part of the code that you aren’t entirely sure of.  Determining from the output that something is wrong is also sometimes hard – the error may be buried in a mass of otherwise correct output.  Or, the error may never produce a “visible” symptom.

 

Below is a program built to illustrate a problem several of your programs contained.  It also illustrates one of C’s less-endearing properties.

 

/*  Where does malloc place newly-requested regions of memory? */

#include <stdio.h>

#include <stdlib.h>

 

int main() {

    char *c, *d, *e;

    int i;

 

    c=(char *) malloc(5);

    d=(char *) malloc(10);

    e=(char *) malloc(7);

 

    for (i=0;i<7;i++) e[i]="qwertyuiop"[i];

    for (i=0;i<12;i++) d[i]="abcdefghijklmnop"[i];

    for (i=0;i<30;i++) c[i]="1234567890qwertyuiop[]asdfghjkl;'zxcvbnm,./"[i];

 

    printf("c='%s'\n",c);

    printf("e='%s'\n",e);

    printf("d='%s'\n",d);

 

    free(d);

    free(c);

    free(e);

 

Below is the output I got:

eenie raw> gcc -g -o testmalloc *.c

eenie raw> testmalloc

c='1234567890qwertyuiop[]asdfghjk'

e='uiop[]asdfghjk'

d='abcdefghijkl'

 

Let’s discuss (a) what is wrong with this program;  (b) how we can determine WHY the errors are and are not revealed by the output;  (c) how this relates to problems some of you had with malloc().

 

The following material may be helpful:  Debugging with gdb

 

Scanf() and pointers

 

One of the uses of pointers is to allow many results to be returned from a sub-program.  C passes all arguments “by value”.  That means that when an expression is given as an argument, the VALUE of the expression is passed into the procedure called, and that value is available as the initial value of the corresponding “parameter” – the variable which “gets” the argument – inside the procedure.  This is a fine scheme, but has the property that there is apparently no way for the procedure to change any of its arguments.  That is, arguments cannot be used to “return values through”.  So procedures are apparently limited to returning only one value.

 

How then are we to write a procedure like scanf(), which wants to read decimal numbers from stdin, and return their values to the program that called scanf()?  The first argument to scanf() is a “format string” that is similar to the format string argument for printf().  Both strings contain embedded “conversion specifiers” that specify, in the scanf() case, that the next item on the input is to be converted in a certain way, and the result used to change something in the calling program.  How to allow this?

 

The trick:  Make the arguments POINTERs to variables that scanf() should change.  The pointer IS a value – the location of the variable.  Suppose parameter q has been declared appropriately, and is passed a pointer to variable Z, outside the call to scanf().  Then inside scanf(), writing *q = <some expression>; will assign the value of <some expression> to variable Z.  This approach is common in C programs.

 

Example:

 

int Z=0;
scanf(“%d”, &Z);

 

will read the next integer (string of decimal digits, after skipping white-space) from stdin, convert it to an internal integer, and store the reulst into variable Z.

 

Note:  Leaving the & off will cause serious trouble!  (What do you think will happen?)

 

A simplified description of scanf() can be found in the document below:

C Input Output.htm