C for Yourself - part 8

Functions are the mainstay of C; all high level operations use them. David Matthewman explains how they work.

The C programming language depends upon functions to an almost unhealthy degree. As I explained in the first of the series back in August, C has very few keywords. There is no PRINT statement, for instance, nor a DIM statement. These operations are instead carried out by functions: printf() and malloc() respectively.

Functions, while they may look similar to keywords, are actually separate pieces of C code. When the program encounters a function, it jumps to that section of code, executes it, and then returns to whatever it was doing before. Functions can be in the same piece of code as the main program or in a completely different program. Both printf() and malloc() are part of the core library of functions provided by the compiler as ready-compiled code. Any compiler conforming to the ANSI standard must provide these and a host of other functions in a standard set of files.

Return values

Functions in C are similar to FNs in BASIC. They take arguments and return a value, although you don't need to use either. For instance, the printf() function returns the number of characters successfully output, a number which can be accessed by:

no = printf("Some text");

but which is not often of any interest. With the malloc() function, however, the returned value is all-important, being a pointer to the block of memory which it has reserved. Lose this, and there was little point in reserving the memory.

The fact that functions return a value leads on to the fact that they, like variables, have a type. The type of the function is the type of the returned value and can be anything that a variable can: int, char, pointer or whattever.

The return value is given in a return statement:

return whattever;

which leaves the function and sends the value of whattever back to the main program. You should usually ensure that a function returns some value, even if it is zero, otherwise the return value will be undefined which can cause problems if you accidentally refer to it.

This is not necessary with functions which have been declared void, as these are explicitly forbidden from returning values. These behave more like PROCs in BASIC and can be used when you are sure that no return value will be needed.

Passing arguments

Arguments are passed to functions in brackets after the function name. The malloc() funcion takes one argument which is the size of memory to be reserved. When a function is declared, not only does the type of the function have to be stated, but also the types of all the function's arguments. (There are exceptions to this, but we will ignore them for the moment.)

A function looks like this:

function_type function(var_list)
{
  var_type variable1;

...

  statement1;
  
...

  statement63;

...

  return some_value;
}

The list of variables var_list is a list of variable declarations:

(type1 var1, type2 var2, type3 var3)

and so on, however many the function takes.

The return statement does not need to be at the end of the function, nor need it be unique.

All the programs that we have seen so far have actually been one single, special function, main(). main() is the first function called whenever a program is run, and every program must have a main() function. Strictly speaking, main() has type int and should return a value, usually zero to indicate no errors. This is because the OS running the program may check this value to ensure that the program has run correctly, and OSs which do this will complain if the return value is not defined.

The scope of variables

So far in C we have treated variables as though any variable can be used anywhere in a program, once it has been declared. This is because if we declare a variable at the start of main() we are free to use it anywhere within main(). However, functions outside main() cannot read from or write to the variable, it is 'invisible' to them and said to be local to main(). This is generally true; any variable declared within a level of braces is said to be local to that level, the level is said to be the variable's scope.

This naturally applies to the variables declared in the argument list of a function, which are local to the function. Even if the variable has the same name as a variable in main(), it will occupy a different address in memory and will be treated as a different variable. (Incidentally, this applies to other C constructions which use braces, such as loops and if statements which we shall cover soon.) This can often help - for instance you can use i as a general loop variable without checking that the program that calls the function does not use i as well - but can also confuse you if you start using variables with the same name for different purposes. The program may be able to remember that they are different, but can you?

It follows from all this that a variable declared outside from any function - including main() - is a global variable and can be 'seen' by the whole program. These may be necessary but are best kept to a minimum. Functions themselves are in effect globally-declared variables.

Fortunately, the compiler will usually detect if you declare a variable 'twice' when you shouldn't, for instance by declaring a local variable in a function with the same name as a global variable.

An example

The listing below - reproduced in an extended version on the disc - shows how functions can be used. The listing contains four functions - add(), subtract(), multiply() and divide() - as well as main(), of course. Note that each function has a type, generally int, but double for divide() as it may not return an integer, and each takes two integer arguments. All the functions refer to these arguments as x and y, but each has its own 'private' versions of x and y, so that they don't interfere with each other. The opposite is true of the character variable error which is visible to all the functions in the program as the same variable. This is because it is declared at the start of the program, outside any function definitions, at the 'zeroth level' of braces in the program. It is used to flag a 'divide by zero' error in the divide() function.

Note also that the multiply() and divide() functions avoid using the extra result variable by doing the calculation within the return statement.

Next issue variable passing will be looked at in more depth.

char error = 'n';

int add(int x, int y)
{
  int result;
  
  result = x + y;
  return result;
}

int subtract(int x, int y)
{
  int result;
  
  result = x - y;
  return result;
}

int multiply(int x, int y)
{
  return x * y;
}

double divide(int x, int y)
{
  if (y == 0)
  {
    error = 'z';
    return 0;
  }
  return x / y;
}

main()
{
  int arg1, arg2, sum, difference, product;
  double dividend;
  
  arg1 = 20; arg2 = 15;
  sum = add(arg1, arg2);
  difference = subtract(arg1, arg2);
  product = multiply(arg1, arg2);
  dividend = divide(arg1, arg2);
  return 0;
}


Source: Acorn User - 152 - February 1995
Publication: Acorn User
Contributor: David Matthewman