A Little C Primer/C Functions in Detail

As noted previously, any C program must have a "main()" function to contain the code executed by default when the program is run.

A program can contain as many functions as needed. All functions are "visible" to all other functions. For example:

   /* fdomain.c */

   #include <stdio.h>;

   void func1( void );
   void func2( void );
   
   int main()
   {
     puts( "MAIN" );
     func1();
     func2();
   }

   void func1( void )
   {
     puts( "FUNC1" );
     func2();
   }

   void func2( void )
   {
     puts( "FUNC2" );
     func1();
   }

In this example, "main()" can call "func1()" and "func2()"; "func1()" can call "func2()"; and "func2()" can call "func1()". In principle, even

"main()" could be called by other functions, but it's hard to figure out why anyone would want to do so. Although "main()" is the first function in the listing above, there's no particular requirement that it be so, but by convention it always should be.

Functions can call themselves recursively. For example, "func1()" can call "func1()" indefinitely, or at least until a stack overflow occurs. You cannot declare functions inside other functions.

Functions are defined as follows:

   float sphere( int rad )
   { 
      ...
   }

They begin with a function header that starts with a return value type declaration ("float" in this case), then the function name ("sphere"), and finally the arguments required ("int rad").

ANSI C dictates that function prototypes be provided to allow the compiler to perform better checking on function calls:

   float sphere( int rad );

For an example, consider a simple program that "fires" a weapon (simply by printing "BANG!"):

   /* bango.c */

   #include <stdio.h>

   void fire( void );

   void main()
   {
     printf( "Firing!\n" );
     fire();
     printf( "Fired!\n" );
   }

   void fire( void )
   {
     printf( "BANG!\n" );
   }

This prints:

   Firing!
   BANG!
   Fired!

Since "fire()" does not return a value and does not accept any arguments, both the return value and the argument are declared as "void"; "fire()" also does not use a "return" statement and simply returns automatically when completed.

Let's modify this example to allow "fire()" to accept an argument that defines a number of shots. This gives the program:

   /* fire.c */

   #include <stdio.h>

   void fire( int n );

   void main()
   {
     printf( "Firing!\n" );
     fire( 5 );
     printf( "Fired!\n" );
   }

   void fire( int n )
   {
     int i;
     for ( i = 1; i <= n ; ++i )
     {
       printf( "BANG!\n" );
     }
   }

This prints:

   Firing!
   BANG!
   BANG!
   BANG!
   BANG!
   BANG!
   Fired!

This program passes a single parameter, an integer, to the "fire()" function. The function uses a "for" loop to execute a "BANG!" the specified number of times—more on "for" later.

If a function requires multiple arguments, they can be separated by commas:

   printf( "%d times %d = %d\n", a, b, a * b );

The word "parameter" is sometimes used in place of "argument". There is actually a fine distinction between these two terms: the calling routine specifies "arguments" to the called function, while the called function receives the "parameters" from the calling routine.

When a parameter is listed in the function header, it becomes a local variable to that function. It is initialized to the value provided as an argument by the calling routine. If a variable is used as an argument, there is no need for it to have the same name as the parameter specified in the function header.

For example:

  fire( shots );
  ...
  void fire( int n )
  ...

The integer variable passed to "fire()" has the name "shots", but "fire()" accepts the value of "shots" in a local variable named "n". The argument and the parameter could also have the same name, but even then they would remain distinct variables.

Parameters are, of course, matched with arguments in the order in which they are sent:

   /* pmmatch.c */

   #include <stdio.h>

   void showme( int a, int b );

   void main()
   {
     int x = 1, y = 100;
     showme( x, y );
   }

   void showme( int a, int b )
   {
     printf( "a=%d  b=%d\n", a, b );
   }

This prints:

   a=1  b=100

This program can be modified to show that the arguments are not affected by any operations the function performs on the parameters, as follows:

   /* noside.c */

   #include <stdio.h>

   void showmore( int a, int b );

   void main()
   {
      int x = 1, y = 100;
      showmore( x, y );
      printf( "x=%d  y=%d\n", x, y );
   }

   void showmore( int a, int b )
   {
      printf( "a=%d  b=%d\n", a, b );
      a = 42;
      b = 666;
      printf( "a=%d  b=%d\n", a, b );
   }

This prints:

   a=1  b=100
   a=42  b=666
   x=1  y=100

Arrays can be sent to functions as if they were any other type of variable:

   /* fnarray.c */

   #include <stdio.h>
   #define SIZE 10
   
   void testfunc( int a[] );
   
   void main()
   {
     int ctr, a[SIZE];
     for( ctr = 0; ctr < SIZE; ++ctr )
     {
       a[ctr] = ctr * ctr;
     }
     testfunc( a );
   }
   
   void testfunc( int a[] )
   {
     int n;
     for( n = 0; n < SIZE; ++ n )
     {
       printf( "%d\n", a[n] );
     }
   }

It is possible to define functions with a variable number of parameters. In fact, "printf()" is such a function. This is a somewhat advanced issue and we won't worry about it further in this document.

The normal way to get a value out of a function is simply to provide it as a return value. This neatly encapsulates the function and isolates it from the calling routine. In the example in the first section, the function "sphere()" returned a "float" value with the statement:

   return( result );

The calling routine accepted the return value as follows:

   volume = sphere( radius );

The return value can be used directly as a parameter to other functions:

   printf( "Volume: %f\n", sphere( radius ) );

The return value does not have to be used; "printf()", for example, returns the number of characters it prints, but few programs bother to check.

A function can contain more than one "return" statement:

   if( error == 0 )
   {
     return( 0 );
   }
   else
   {
     return( 1 );
   }

A "return" can be placed anywhere in a function. It doesn't have to return a value; without a value, "return" simply causes an exit from the function. However, this does imply that the data type of the function must be declared as "void":

   void ftest( int somevar )
   {
      ...
      if( error == 0 )
      {
        return();
      }
      ...
   }

If there's no "return" in a function, the function returns after it executes its last statement. Again, this means the function type must be declared "void".

The "return" statement can only return a single value, but this value can be a "pointer" to an array or a data structure. Pointers are a complicated subject and will be discussed in detail later.