C - From The Top (part 5 - Pointing and Functions)

Functions

A program is made up from a series of functions, this much we already know. What I haven't told you is that, besides the standard int main(void) function, you can have other functions and function types.

There are lots of function types, which follow the different variable types with one exception, void. We have seen what the int function does, but what is a void function, and can arguments be passed over to them?

A void function is normally one which doesn't really do very much - it may just output a line of text to say that the function being used hasn't been completed yet. That's not to say a void function is useless - far from it.

The void function is useful because arguments can be passed over to them. For instance, say I had a program which needed some work performed on a set of variables, I could enter the same piece of code a fair number of times, but as long as the maths is the same each time, a void function could be used to save time in a data processing situation.

Usage

void get_a_name(void);
char line[40]; // global var
int main(void)
{
 get_a_line(); // calls the function
 printf("%s",line);
 return 0;
}
void get_a_line(void)
{
 printf("Enter something : ");
 gets(line);
}

You will notice that the function is also declared before the int main(void) line. This is called the prototype for the function. This declares the function before it is used , the return type, the name and parameter list.

The parameter list doesn't need to be empty; it can contain any number of arguments, but the variable type has to be maintained between the protocol and the actual function. For instance

PROTOCOL : 
void rpc(int mem, char name);
FUNCTION : 
void rpc(int memory, char title)

The variable types have been maintained, but not the variable names - these don't matter. (Remember in Basic, if you have lines such as:

PROCrpc(a%,a$)
DEFPROCrpc(memory%,title$)

This is fine. The first argument may be called a% in the call, but will be processed as memory% in the proc.)

One important thing to remember though is that a void function returns no values.

You can have other function types which return other variable types:

double fn_name(...) returns a double value
char fn_name (...) returns a char

You get the idea!

I will return to functions in a later piece, to cover arguments in the int main(...) function and functions across files.

Pointers & pointers to pointers

When defining a variable, a set amount of memory is reserved for each variable type. If this is used in (say) a scanf("%d",&var); line, the contents of var are written to this address as well as being stored in var. What a pointer does is point to the start of the address where var is kept. It has to be handled slightly differently though.

Normally, you would have

int var;

but for the pointer, you would need

int *var;

The * means that it is a pointer.

If the int *var is declared, no space is allocated to var - it's simply a starting point for an address. The int defines the maximum space it will occupy.

Pointers come into their own when passing over a variable to a function.

The normal practice is to pass a single value or character string. However, although in C the single value is simple enough, the string is held in an array, so it is the entire array which has to be passed over.

In the examples up to now, the biggest array has been 80 characters - this isn't going to slow even an A3000 down by much. However, say I had a three-dimensional array of floating point variables 300 × 300 × 300 - a gross size of 27,000,000 numbers. This isn't impossible (indeed, quite a number of my PhD programs do this as a matter of course!), but the amount of time taken to pass all that data around a program would be terrible.

The way around this would be to call the function using a pointer.

The array would be set up as

float *array[300][300][300];

and the function called would be

do_something=funct(array);

and the protocol would be

double funct(double *array);

A simple way to understand the pointer is to try this program (type this in and run it).

#include <stdio.h>
int main(void)
{
 int var1, *pointer;
 var1 = 1024
 pointer = &var1;
 printf ("%d",*pointer);
 return 0;
}

This declares two variables: var1 and pointer. The value of var1 is set to be 1024; pointer is then set to be the start address of var1 , but not the contents of that place. The value of the memory location pointed to by pointer is then printed.

This can also be swapped around thus

int main(void)
{
 int var1, *pointer;
 pointer=&var1;
 *pointer=1024
 printf("var1 = %d",var1);
 return 0;
}

Here pointer has been set at the address reserved for var1. Next, 1024 has been written to this address via the *pointer line (Basic equivalent: !address); var1 is then written to the screen (again, it will display 1024). It has displayed 1024 because var1's memory location contains 1024 instead of 0.

The pointer to a pointer is denoted by a double asterisk (**).

The usage is:

char **dbl_pointer;

This type of pointer points to the memory location of a normal pointer which in turn points to a variable:

char **dbl_point, *point, var;
point = &var
dbl_point = &point
**dbl_point='x';

Here, pointer gets the address of var while dbl_point gets the address of pointer. As with the second example, var is then given the character 'x' (note the type of quotes used) through dbl_point -> point -> var .

OK, this may not seem very useful, but just wait... I'll show you in due course.

Next on my list for you are arrays and then more on the printf command.

Until then, try this one out... Rewrite either version of the simple calculator using multiple functions. However, this time you must add the following functions: power, average and sum.

These three should provide you with some entertainment. Remember, a power is simply the number multiplied by itself however many times (a for loop would be helpful here); the average can be performed using two variables, and sum is basically the same as average with the exception that it's not divided by the number of entries. If you felt really clever, you could use the same function for both average and sum. At least one of these functions must be a pointer function.

Remember to declare the protocols before the int main(void) statement.

However, an interesting point to note is that if you compare the compiled file sizes of the original program (which contained five types of processing) and the new version (which contains eight), you will see that there is roughly 500 bytes' difference. For this 500 bytes, you've made the program far more expandable and far simpler to debug!


Source: Archive Magazine 13.5
Publication: Archive Magazine
Contributor: Paul Johnson