Functions & Pointers In C Language - Online Article

Almost all programming languages have some equivalent of the function. You may have met them under the alternative names subroutine or procedure. Some languages distinguish between functions which return variables and those which don't. C assumes that every function will return a value. If the programmer wants a return value, this is achieved using the return statement. If no return value is required, none should be used when calling the function. Here is a function which raises a double to the power of an unsigned, and returns the result.

double power(double val, unsigned pow)
{
	double ret_val = 1.0;
	unsigned i;
	for(i = 0; i < pow; i++)
		ret_val *= val;
	return(ret_val);
}

 

The function follows a simple algorithm, multiplying the value by itself pow times. A for loop is used to control the number of multiplications, and variable ret_val stores the value to be returned. Careful programming has ensured that the boundary condition is correct too. ie . Let us examine the details of this function.

double power(double val, unsigned pow)

This line begins the function definition. It tells us the type of the return value, the name of the function, and a list of arguments used by the function. The arguments and their types are enclosed in brackets, each pair separated by commas. The body of the function is bounded by a set of curly brackets. Any variables declared here will be treated as local unless specifically declared as static or extern types.

return(ret_val);

On reaching a return statement, control of the program returns to the calling function. The bracketed value is the value which is returned from the function. If the final closing curly bracket is reached before any return value, then the function will return automatically, any return value will then be meaningless. The example function can be called by a line in another function which looks like this:

result = power(val, pow);

This calls the function power assigning the return value to variable result. Here is an example of a function which does not return a value.

void error_line(int line)
{
	fprintf(stderr, "Error in input data: line %d\n", line);
} 

The definition uses type void which is optional. It shows that no return value is used. Otherwise the function is much the same as the previous example, except that there is no return statement. Some void type functions might use return, but only to force an early exit from the function, and not to return any value. This is rather like using break to jump out of a loop. This function also demonstrates a new feature.

fprintf(stderr, "Error in input data: line %d\n", line);

This is a variant on the printf statement, fprintf sends its output into a file. In this case, the file is stderr. stderr is a special UNIX file which serves as the channel for error messages. It is usually connected to the console of the computer system, so this is a good way to display error messages from your programs. Messages sent to stderr will appear on screen even if the normal output of the program has been redirected to a file or a printer.

Scope of Function Variables

Only a limited amount of information is available within each function. Variables declared within the calling function can't be accessed unless they are passed to the called function as arguments. The only other contact a function might have with the outside world is through global variables. Local variables are declared within a function. They are created anew each time the function is called, and destroyed on return from the function. Values passed to the function as arguments can also be treated like local variables. Static variables are slightly different, they don't die on return from the function. Instead their last value is retained, and it becomes available when the function is called again. Global variables don't die on return from a function. Their value is retained, and is available to any other function which accesses them.

Modifying Function Arguments

Some functions work by modifying the values of their arguments. This may be done to pass more than one value back to the calling routine, or because the return value is already being used in some way. C requires special arrangements for arguments whose values will be changed. You can treat the arguments of a function as variables, however direct manipulation of these arguments won't change the values of the arguments in the calling function. The value passed to the function is a copy of the calling value. This value is stored like a local variable, it disappears on return from the function.

There is a way to change the values of variables declared outside the function. It is done by passing the addresses of variables to the function. These addresses, or pointers, behave a bit like integer types, except that only a limited number of arithmetic operators can be applied to them. They are declared differently to normal types, and we are rarely interested in the value of a pointer. It is what lies at the address which the pointer references which interests us.

To get back to our original function, we pass it the address of a variable whose value we wish to change. The function must now be written to use the value at that address (or at the end of the pointer). On return from the function, the desired value will have changed. We manipulate the actual value using a copy of the pointer.

Pointers

Pointers are not exclusive to functions, but this seems a good place to introduce the pointer type. Imagine that we have an int called i. Its address could be represented by the symbol &i. If the pointer is to be stored as a variable, it should be stored like this.

int *pi = &i;

int * is the notation for a pointer to an int. & is the operator which returns the address of its argument. When it is used, as in &i we say it is referencing i. The opposite operator, which gives the value at the end of the pointer is *. An example of use, known as de-referencing pi, would be:

i = *pi;

Take care not to confuse the many uses of the * sign; Multiplication, pointer declaration and pointer de-referencing. This is a very confusing subject, so let us illustrate it with an example. The following function fiddle takes two arguments, x is an int while y is a pointer to int. It changes both values.

fiddle(int x, int *y)
{
	printf(" Starting fiddle: x = %d, y = %d\n", x, *y);
	x ++;
	(*y)++;
	printf("Finishing fiddle: x = %d, y = %d\n", x, *y);
} 

since y is a pointer, we must de-reference it before incrementing its value. A very simple program to call this function might be as follows.

main()
{
	int i = 0;
	int j = 0;
	printf(" Starting main  : i = %d, j = %d\n", i, j);
	printf("Calling fiddle now\n");.
	fiddle(i, &j);
	printf("Returned from fiddle\n");
	printf("Finishing main  : i = %d, j = %d\n", i, j);
} 

Note here how a pointer to int is created using the & operator within the call fiddle(i, &j);. The result of running the program will look like this.

Starting main: i = 0, j = 0
Calling fiddle now
Starting fiddle: x = 0, y = 0
Finishing fiddle: x = 1, y = 1
Returned from fiddle Finishing main: i = 0, j = 1

After the return from fiddle the value of i is unchanged while j, which was passed as a pointer, has changed. To summarise, if you wish to use arguments to modify the value of variables from a function, these arguments must be passed as pointers, and de-referenced within the function. Where the value of an argument isn't modified, the value can be passed without any worries about pointers.

Arrays and Pointers

To fully understand the workings of C you must know that pointers and arrays are related. An array is actually a pointer to the 0th element of the array. Dereferencing the array name will give the 0th element. This gives us a range of equivalent notations for array access. In the following examples, arr is an array.

Image1

There are some differences between arrays and pointers. The array is treated as a constant in the function where it is declared. This means that we can modify the values in the array, but not the array itself, so statements like arr ++ are illegal, but arr[n] ++ is legal. Since an array is like a pointer, we can pass an array to a function, and modify elements of that array without having to worry about referencing and de-referencing. Since the array is implemented as a hidden pointer, all the difficult stuff gets done automatically.

A function which expects to be passed an array can declare that parameter in one of two ways.

Image2

Either of these definitions is independent of the size of the array being passed. This is met most frequently in the case of character strings, which are implemented as an array of type char. This could be declared as char string[]; but is most frequently written as char *string; In the same way, the argument vector argv is an array of strings which can be supplied to function main. It can be declared as one of the following.

Image3

Don't panic if you find pointers confusing. While you will inevitably meet pointers in the form of strings, or as variable arguments for functions, they need not be used in most other simple types of programs.

Recursive Functions

A recursive function is one which calls itself. This is another complicated idea which you are unlikely to meet frequently. We shall provide some examples to illustrate recursive functions. Recursive functions are useful in evaluating certain types of mathematical function. You may also encounter certain dynamic data structures such as linked lists or binary trees. Recursion is a very useful way of creating and accessing these structures. Here is a recursive version of the Fibonacci function. We saw a non recursive version of this earlier.

int fib(int num)
/* Fibonacci value of a number */
{
	switch(num)
	{
		case 0:
			return(0);
		break;
		case 1:
			return(1);
		break;
		default:
			/* Including recursive calls */
			return(fib(num - 1) + fib(num - 2));
		break;
	}
}

We met another function earlier called power. Here is an alternative recursive version.

double power(double val, unsigned pow)
{
	if(pow == 0)
		/* pow(x, 0) returns 1 */
		return(1.0);
	else
		return(power(val, pow - 1) * val);
}

Notice that each of these definitions incorporate a test. Where an input value gives a trivial result, it is returned directly, otherwise the function calls itself, passing a changed version of the input values. Care must be taken to define functions which will not call themselves indefinitely, otherwise your program will never finish. The definition of fib is interesting, because it calls itself twice when recursion is used. Consider the effect on program performance of such a function calculating the fibonacci function of a moderate size number.

Image4

If such a function is to be called many times, it is likely to have an adverse effect on program performance. Don't be frightened by the apparent complexity of recursion. Recursive functions are sometimes the simplest answer to a calculation. However there is always an alternative non-recursive solution available too. This will normally involve the use of a loop, and may lack the elegance of the recursive solution.

About the Author:

No further information.




Comments

No comment yet. Be the first to post a comment.