Introduction to C Programming by Rob Miles, Electronic Engineering


Functions

Functions So Far
Function Heading
Function Body
return
Calling a Function
Scope
Variables Local to Blocks
Full Functions Example
Pointers
NULL Pointers
Pointers and Functions
Static Variables

Functions So Far

We have already come across the functions main, printf and scanf. When you write a program of any size you will find that you can use functions to make life a whole lot easier.

In the glazing program above we spend a lot of time checking the values of inputs and making sure that they are in certain ranges. We have exactly the same piece of code to check widths and heights. If we added a third thing to read, for example frame thickness, we would have to copy the code a third time. This is not very efficient, it makes the program bigger and harder to write. What we would like to do is write the checking code once and then use it at each point in the program. To do this you need to define a function to do the work for you.

Functions are identified by name and can return a single value. They act on parameters, which are used to pass information into them. In our case we want a function which is given the maximum and minimum values which something can have, and returns a value in that range.

We need to tell the compiler the name of our function, what parameters it has, and the type of information it returns.

Function Heading

We do all this in the function heading

float get_ranged_value ( float min, float max )

This tells the compiler that we are defining a function called get_ranged_value. The first word, float, tells the compiler we are defining a function which will return the value of a floating point number. The second item is the function name, i.e. a name that we wish our function to be known by. You create a function name using exactly the same rules as for a variable name.

The next item is a list of parameter names, enclosed in brackets. We have two parameters, min and max. We must then tell the compiler the type of these two parameters, in this case they are both floating point numbers.

Note that C does not do that much checking to make sure that you are giving the right kinds of parameters in the correct order. If you get this wrong the program will fail, probably in a very confusing way.

Function Body

We then follow this definition with the body of the function, which is in fact a block, making our complete function as follows:

float get_ranged_value ( float min, float max )
{
	float temp ;
	do {
		printf ( "Give the value : " ) ;
		scanf ( "%f", &temp ) ;
		} while ( (temp < min) || (temp > max) ) ;
		return temp ;
}

return

The body of the function looks very like the code which we originally wrote, however there is one additional line :

return temp ;

This is the means by which the function returns the value to whatever called it. If our function has a type other than void it must return a value of the appropriate type. You return a value by placing it in the program, after the return keyword.

You can return at any point during a function. This can be used as a very neat "get the hell out of here".

Calling a Function

When the compiler sees get_ranged_value it knows that this refers to a previously defined procedure. It takes the values MAX_WIDTH and MIN_WIDTH and feeds them into the function. You can use max and min as variables within the function, initially they are set to the values of the appropriate parameter. Any changes to these parameters which you make inside the function are not passed on to the outside world. When you reach the return line in the function this causes the function to finish and pass back the value following the return. Of course you must return something of the correct type, otherwise the compiler will moan at you and nasty things might happen.

In our main program we would write something like :

width = get_ranged_value ( MIN_WIDTH, MAX_WIDTH ) ;

Local function variables

Note that inside our function we have declared a variable, temp. This is used to hold the value which the user types in, so that we can compare it with the maximum and minimum. This is a local variable. We only want to use temp during this function, so we tell the compiler that it is only declared inside the function. When get_ranged_value finishes the variable is discarded. This is useful for two reasons:

It saves space. We are not reserving space for a variable when we are not going to use it. temp only comes into existence when required and the memory it uses is available at other times.

It reduces confusion. Because temp is only declared within get_ranged_value that is the only place you can use it.

If I mention temp in another part of the program the compiler will say that it cannot see it. This means that if someone else has written a function which uses a local variable called temp there will be no confusion.

Scope

When talking about variables in this way you often hear the word scope. Any variable has a given scope. You could describe the scope of a variable as that portion of a program within which it is meaningful to use that variable.

If you declare a variable outside a function it is effectively accessible everywhere. Such variables are often called global, i.e. their scope is the entire program. You have to be careful when you make a variable global. The fact that it can be used in any part of the program means that it can be corrupted in any part of the program.

Up until now all our variables have been declared to be global. This is not good practice. Only certain values are that important should be global. One part of program design is deciding which of the variables need to be made global. This is particularly valuable when several people are working on one project. If you ever get involved with the writing of a large system the trick is to get together first and decide what global variables to use, then decide on the overall structure, what each section is going to do and how they are going to exchange data. You can then go ahead and start writing your part of the system, secure in the knowledge that you will not be causing anyone else problems.

As an example of variables and scope consider the following:

int fred ;                                     |
void main ()                                   |
{                                              |
        float fred ;        |
        ....                | scope of
        ....                | local fred
}                                              |
                                               | 
void road ()                                   | scope of
{                                              | global
        int jim ;           |                  | fred
        ....                | scope of         |
        ....                | jim              |
}                           |                  |

The global fred cannot be accessed by the function main, any reference to fred refers to the local variable. Within main this variable is said to be scoped out.

I adopt a little convention when choosing variable names. If the variable is going to be global I start the name with a capital letter, local variables are all lower case, for example:

Window_Total

- would be a global variable whereas

counter

-would be local.

Variables Local to Blocks

You can extend the idea of little local variables even further, in that any block can contain a variable definition which will last for the duration of that block. When that block finishes the variable disappears. This is especially useful if you need a little counter for a job in the middle of a program:

This is a screen clear routine which will work on any machine!
{
	int i ;
	for ( i=0 ; i < 25 ; i++ ) {
		printf ( "\n" ) ;
	}
}

The variable i only exists for the duration of the block, so this loop does not interfere with any other variables called i which might be lying around your program. If you have a sudden local need for a variable for a specific task it makes very good sense to create one there and then to do the job. You could argue that it is not very efficient to do this, because the variable is created each time the block is entered, but it makes the program very much clearer and reduces the chances of variable names clashing. Note that you must declare any block variables at the very start of the block.

Full Functions Example

This is the definitive double glazing example:

/* Functions Double Glazing Program */
/* Rob Miles Nov. 1990	 */

#include <stdio.h>

/* Define our window size range */
#define MAX_HEIGHT 3.0
#define MAX_WIDTH 5.0
#define MIN_HEIGHT 0.75
 #define MIN_WIDTH 0.5

/* Define a few costs */
#define COST_TO_MAKE 2.00 
#define WOOD_COST 2.00 
#define GLASS_COST 3.00 
#define MARKUP_FACTOR 2.25

/* Define the maximum number of windows on our house */
#define MAX_WINDOWS 10

/* Function to get a value and validate its range	*/
/* returns a floating point value within min and max */
float get_ranged_value ( float min, float max )
{
/* get_ranged_value variables :	 */
/* temp holds the value we are looking at.	 */
	float temp ;
	do {
		printf ( " enter the value : " ) ;
		scanf ( "%f", &temp ) ;
	} while ( (temp < min) || (temp > max) ) ;
	return temp ;
}

void main ()
{
/* main variables : */
/* width - width of current window */
/* height - height of current window */
/* window_cost - cost to make the window */
/* window_sell - amount we sell the window for */
/* house_cost - cost to do the whole house */
/* house_sell - amount we sell the house job for */
	float width, height, window_cost, window_sell, house_cost,
		house_sell ;
/* no_of_windows - number of windows in the house */
/* window_count - counter for current window */
	int no_of_windows, window_count ;
	printf ( "Double Glazing House Calculator\n" ) ;
	printf ( "Rob Miles November 1990\n" ) ;
	do {
		printf ( "Give the number of windows in the house : " ) ;
		scanf ( "%d", &no_of_windows ) ;
	} while ( (no_of_windows <= 0) ||
		(no_of_windows > MAX_WINDOWS) ) ;
	house_cost = 0.0 ;
	house_sell = 0.0 ;
	for (	window_count = 1 ;
		 window_count <= no_of_windows ; 
		window_count++ ) {
		printf ( "Enter the details of window %d\n",
		window_count ) ;
		printf ( " enter the width\n" ) ;
		width = get_ranged_value ( MIN_WIDTH, MAX_WIDTH ) ;
		printf ( " enter the height\n" ) ;
		height = get_ranged_value ( MIN_HEIGHT,
			MAX_HEIGHT ) ;
		window_cost = WOOD_COST * 2 * (width + height) ; 
		window_sell = window_cost * MARKUP_FACTOR ;
		printf (
		"Window %d costs %.2f to make and sell for %.2f.\n\n",
		window_count, window_cost, window_sell ) ;
		house_cost += window_cost ;
		house_sell += window_sell ;
	}
	printf ( "\nTotal cost for %d windows : %.2f.\n",
		no_of_windows, house_cost ) ;
	printf ( "\nTotal sale price for %d windows : %.2f.\n",
	no_of_windows, house_sell ) ;
}

Pointers

For now we have only looked briefly at pointers, just using them to tell scanf where to put values it fetches for us. However, pointers are much more than this, in fact they are an integral part of C. Unfortunately pointers are also painfully difficult to understand, so do not feel upset if they do not make sense immediately. Just keep looking at the examples until something makes sense!

A pointer is in fact just a different kind of variable. You can regard a normal variable as a box with a name on it. You can regard a pointer as a tag on the end of a piece of rope. By following the rope you can get to a box, into which you can put something

Note that to keep things hunky-dory , C says that pointers can only point to items of a particular type. Think of it like this, all the integer boxes are red, all the float boxes are green and so on. The rope of a pointer is also colour co-ordinated, which means that if you try to tie a piece of red string onto a green box (use an int pointer to point at a float value) the compiler will raise aesthetic objections and complain about your colour scheme!

You tell C that you are declaring a pointer to a type, rather than a variable of that type, by putting a * in front of the name of the variable, i.e.

i is defined as an integer variable. ptr is defined as a pointer to integers. Remember that neither variable has anything useful in it just after declaration, i.e. i contains a silly value and ptr points nowhere useful.

To find the address of something, i.e. the value you put in a pointer to make it point at it, we use the & character.

We have already met &. It means create a a piece of rope which is tied to the box which holds this variable. The following line of code :

ptr = &i ;

- does the following :

ptr now points at i. If I want to change the contents of the box that ptr points at I can use the * operator to de-reference the pointer and get at the contents.

*is new. It means follow the rope on this pointer to the box it is fixed to and use the value in the box.

* and & are complementary. You will use & when you have a variable which you want to make a pointer to and * when you want to refer to the contents of a box which a pointer is pointing at (if this confuses you at the moment just think in terms of pointers being bits of rope tied to boxes and variables being boxes with names on...).

The upshot of all this is that, once we have made ptr point to i, the following two lines of code have exactly the same effect :

i = 99 ;
*ptr = 99 ;

If you want to have a quick way of remembering what is going on consider this :

int i ;
*(&i) = 99 ;

- this is actually legal! It means make a pointer to i (that is what the & does) and then de-references it (that is what the * does). Then put 99 into the place that the pointer to i points to, i.e. it is functionally equivalent to :

i = 99 ;

- we put the brackets in to keep the compiler happy and tell it the order in which things are happening.

As you might expect, if you have not given a value to a pointer then it is tied on to most any old thing, and referring to the contents of an undefined pointers is one of the shorter ways to explode a program....

Consider the following program:

#include <stdio.h>
void main ()
{
	int fred, jim ;
	int *pointer1, *pointer2 ;
	pointer1 = &fred ;
	pointer2 = &jim ;
	pointer1 = 99 ; fred = 100 ;
	*pointer2 = *pointer1 ;
	printf ( "The value of fred is %i.\n", fred ) ; 
	printf ( "The value of jim is %i.\n", jim ) ;
}

Note that the * operator can be used on both sides of an assignment, i.e. the contents of the box on the end of this rope will work equally well for putting things into the box as for taking them out. Note also that we are overloading the * operator. This means that we are using it to mean more than one thing, because we also use * to mean multiplication. The C compiler is usually able to make sense of what we give it, and work out what we really mean. However if you are performing multiplication with the contents of pointers it is a good idea to put things inside brackets so as to force the point, e.g.

result = (*value_1_pointer) * (*value_2_pointer) ;

NULL Pointers

It is interesting to note that the way that C works on the PC if you store something where the NULL pointer points you will almost certainly crash the machine!
It is often very useful to be able to denote the fact that a pointer does not point to anything useful. For example, if your function is supposed to set a pointer which points to the item it has found, but it does not find the item, you would find it useful to be able to denote this your program. In the C standard headers, for example stdio.h, there is an address called NULL. This address does not exist, trying to use it will result in your program doing strange things, but it can be used to mean "this pointer does not point anywhere". The C libraries use this extensively, for example if you try to get hold of a block of memory (see malloc later in the notes) but the memory is not available you will instead be given a pointer set to NULL, to indicate that the memory could not be found.

You should never put things where NULL points, instead you should test the value of the pointer against NULL:

if ( memory_base == NULL ) {
	printf ( "No more memory!\n\n" ) ;
	exit (1) ;
}

Pointers and Functions

You can send values into functions very easily, simply by giving them as parameters, in the case of get_ranged_value function we have a parameters of type float, into which we can feed values. However, as things stand, a function can only return a single value, via the return statement. Suppose we wanted to enhance get_ranged_value so that it returned whether or not the user had abandoned the program. We could tell the user that a value of -1 at any time means abort. However, we now have to return more than one thing from our function, both the result and whether or not the function succeeded.

We can only pass information into functions, and they can only return a single value, so this would seem to be impossible. Fortunately we can use pointers to solve this problem, we give the function a pointer to where we want the result putting, just like we do with scanf.

We tell C that a given parameter to a function is a pointer in the same way that we declare a variable which is a pointer. Making these changes leads to the following:

/*  If we get this value as input it means give up  */
#define ABORT_VALUE 0
/*  Function to get a value and validate its range  */
/*  returns TRUE if the value was OK and the abort  */
/*  value was not given.  */
/*  puts a floating point value obtained from the user   */
/*  into the location pointed at by result  */

float get_ranged_value ( float min, float max, float * result ) {
/*  get_ranged_value variables :  */
/*  temp holds the value we are looking at.  */
	float temp ;
	do {
		printf ( " enter the value : " ) ;
		scanf ( "%f", &temp ) ;
		if (temp == ABORT_VALUE)
			return FALSE ;
	} while ( (temp < min) || (temp > max) ) ;
	*result = temp ;
	return TRUE ;
	}

We can call the function and test the result in the following way:

if (get_ranged_value (MIN_WIDTH, MAX_WIDTH, &width)==FALSE )
	break ;

Because we can call functions anywhere, even in the middle of comparisons, we can get the value and then immediately test the result of the function. In the code snippit above the program will break out of an enclosing loop if the user gives the abort value for the width.

Once you have got the idea of how pointers can be used to get information in and out of functions it is rather simple. In the meantime just use the above function as an example of how to do it and copy how it works! (there is no dishonour in copying chunks of working program into creations of your own, although for assessment purposes we prefer it if the program is mainly your own work!).


Static Variables

When a program returns from a function C deletes all the local variables. You can think of this as C taking all the boxes off the local shelf and throwing them away.

If the function is ever entered again C will make new boxes, paint their names on them and then put them on the local shelf. This means that a particular local variable will cannot be used to hold a value from one call of a function to the next. If you want a variable to last longer than the length of your function you have to make it global. However, as we have already seen, a very good programming trick is to keep the number of global variables to an absolute minimum.

C gets around this problem by allowing another storage class called static. A storage class is an extra piece of information which you give C to tell it more about how you want the variable stored. There are other storage classes, we will come to them later. When you declare a local variable you can put the word static in front of the declaration, for example:

static int fred ;

When C sees this it makes a box as usual, then it paints the name of the box, along with the name of the function within which it was declared, and then puts this box on a third shelf, the static shelf. Unlike the local shelf, the static one is not emptied when the function finishes. Instead the value is kept in case the function is called again. Note that C has to put the name of the function so that, if several functions have a static variable with the same name, it can find the right one.

With a static variable functions can retain variable values over successive calls.


Rob Miles, R.S.Miles@e-eng.hull.ac.uk, Electronic Engineering
HTML by Bronwen Reid, July 1995