C Programming/Procedures and functions

< C Programming

In C programming, all executable code resides within a function. A function is a named block of code that performs a task and then returns control to a caller. Note that other programming languages may distinguish between a "function", "subroutine", "subprogram", "procedure", or "method" -- in C, these are all functions.

A function is often executed (called) several times, from several different places, during a single execution of the program. After finishing a subroutine, the program will branch back (return) to the point after the call.

Functions are a powerful programming tool.

As a basic example, suppose you are writing code to print out the first 5 squares of numbers, do some intermediate processing, then print the first 5 squares again. We could write it like this:

#include <stdio.h>

int main(void)
{
  int i;
  for(i=1; i <= 5; i++)
  {
     printf("%d ", i*i);
  }
  for(i=1; i <= 5; i++)
  {
     printf("%d ", i*i);
  }
  return 0;
}

We have to write the same loop twice. We may want to somehow put this code in a separate place and simply jump to this code when we want to use it. This would look like:

#include <stdio.h>

void Print_Squares(void)
{
  int i;
  for(i=1; i <=5; i++)
  {
    printf("%d ", i*i);
  }
}

int main(void)
{
  Print_Squares();
  Print_Squares();
  return 0;
}

This is precisely what functions are for.

More on functions

A function is like a black box. It takes in input, does something with it, then spits out an answer.

Note that a function may not take any inputs at all, or it may not return anything at all. In the above example, if we were to make a function of that loop, we may not need any inputs, and we aren't returning anything at all (Text output doesn't count - when we speak of returning we mean to say meaningful data that the program can use).

We have some terminology to refer to functions:

Writing functions in C

It's always good to learn by example. Let's write a function that will return the square of a number.

int square(int x)
{
   int square_of_x;
   square_of_x = x * x;
   return square_of_x;
}

To understand how to write such a function like this, it may help to look at what this function does as a whole. It takes in an int, x, and squares it, storing it in the variable square_of_x. Now this value is returned.

The first int at the beginning of the function declaration is the type of data that the function returns. In this case when we square an integer we get an integer, and we are returning this integer, and so we write int as the return type.

Next is the name of the function. It is good practice to use meaningful and descriptive names for functions you may write. It may help to name the function after what it is written to do. In this case we name the function "square", because that's what it does - it squares a number.

Next is the function's first and only argument, an int, which will be referred to in the function as x. This is the function's input.

In between the braces is the actual guts of the function. It declares an integer variable called square_of_x that will be used to hold the value of the square of x. Note that the variable square_of_x can only be used within this function, and not outside. We'll learn more about this sort of thing later, and we will see that this property is very useful.

We then assign x multiplied by x, or x squared, to the variable square_of_x, which is what this function is all about. Following this is a return statement. We want to return the value of the square of x, so we must say that this function returns the contents of the variable square_of_x.

Our brace to close, and we have finished the declaration.

Written in a more concise manner, this code performs exactly the same function as the above:

int square(int x)
{
   return x * x;
}

Note this should look familiar - you have been writing functions already, in fact - main is a function that is always written.

In general

In general, if we want to declare a function, we write

 type name(type1 arg1, type2 arg2, ...)
 {
   /* code */
 } 

We've previously said that a function can take no arguments, or can return nothing, or both. What do we write if we want the function to return nothing? We use C's void keyword. void basically means "nothing" - so if we want to write a function that returns nothing, for example, we write

void sayhello(int number_of_times)
{
  int i;
  for(i=1; i <= number_of_times; i++) {
     printf("Hello!\n");
  }
}

Notice that there is no return statement in the function above. Since there's none, we write void as the return type. (Actually, one can use the return keyword in a procedure to return to the caller before the end of the procedure, but one cannot return a value as if it were a function.)

What about a function that takes no arguments? If we want to do this, we can write for example

float calculate_number(void)
{
  float to_return=1;
  int i;
  for(i=0; i < 100; i++) {
     to_return += 1;
     to_return = 1/to_return;
  }
  return to_return;
}

Notice this function doesn't take any inputs, but merely returns a number calculated by this function.

Naturally, you can combine both void return and void in arguments together to get a valid function, also.

Recursion

Here's a simple function that does an infinite loop. It prints a line and calls itself, which again prints a line and calls itself again, and this continues until the stack overflows and the program crashes. A function calling itself is called recursion, and normally you will have a conditional that would stop the recursion after a small, finite number of steps.

      // don't run this!
void infinite_recursion()
{
    printf("Infinite loop!\n");
    infinite_recursion();
}

A simple check can be done like this. Note that ++depth is used so the increment will take place before the value is passed into the function. Alternatively you can increment on a separate line before the recursion call. If you say print_me(3,0); the function will print the line Recursion 3 times.

void print_me(int j, int depth)
{
   if(depth < j) {
       printf("Recursion! depth = %d j = %d\n",depth,j); //j keeps its value
       print_me(j, ++depth);
   }
}

Recursion is most often used for jobs such as directory tree scans, seeking for the end of a linked list, parsing a tree structure in a database and factorising numbers (and finding primes) among other things.

Static functions

If a function is to be called only from within the file in which it is declared, it is appropriate to declare it as a static function. When a function is declared static, the compiler will now compile to an object file in a way that prevents the function from being called from code in other files. Example:

static int compare( int a, int b )
{
    return (a+4 < b)? a : b;
}

Using C functions

We can now write functions, but how do we use them? When we write main, we place the function outside the braces that encompass main.

When we want to use that function, say, using our calculate_number function above, we can write something like

 float f;
 f = calculate_number();

If a function takes in arguments, we can write something like

 int square_of_10;
 square_of_10 = square(10);

If a function doesn't return anything, we can just say

 say_hello();

since we don't need a variable to catch its return value.

Functions from the C Standard Library

While the C language doesn't itself contain functions, it is usually linked with the C Standard Library. To use this library, you need to add an #include directive at the top of the C file, which may be one of the following:

The functions available are:

<assert.h> <limits.h> <signal.h> <stdlib.h>
  • assert(int)
  • (constants only)
  • int raise(int sig). This
  • void* signal(int sig, void (*func)(int))
  • atof(char*), atoi(char*), atol(char*)
  • strtod(char * str, char ** endptr ), strtol(char *str, char **endptr), strtoul(char *str, char **endptr)
  • rand(), srand()
  • malloc(size_t), calloc (size_t elements, size_t elementSize), realloc(void*, int)
  • free (void*)
  • exit(int), abort()
  • atexit(void (*func)())
  • getenv
  • system
  • qsort(void *, size_t number, size_t size, int (*sortfunc)(void*, void*))
  • abs, labs
  • div, ldiv
<ctype.h> <locale.h> <stdarg.h> <string.h>
  • isalnum, isalpha, isblank
  • iscntrl, isdigit, isgraph
  • islower, isprint, ispunct
  • isspace, isupper, isxdigit
  • tolower, toupper
  • struct lconv* localeconv(void);
  • char* setlocale(int, const char*);
  • va_start (va_list, ap)
  • va_arg (ap, (type))
  • va_end (ap)
  • va_copy (va_list, va_list)
  • memcpy, memmove
  • memchr, memcmp, memset
  • strcat, strncat, strchr, strrchr
  • strcmp, strncmp, strccoll
  • strcpy, strncpy
  • strerror
  • strlen
  • strspn, strcspn
  • strpbrk
  • strstr
  • strtok
  • strxfrm
errno.h math.h stddef.h time.h
  • (errno)
  • sin, cos, tan
  • asin, acos, atan, atan2
  • sinh, cosh, tanh
  • ceil
  • exp
  • fabs
  • floor
  • fmod
  • frexp
  • ldexp
  • log, log10
  • modf
  • pow
  • sqrt
  • offsetof macro
  • asctime (struct tm* tmptr)
  • clock_t clock()
  • char* ctime(const time_t* timer)
  • double difftime(time_t timer2, time_t timer1)
  • struct tm* gmtime(const time_t* timer)
  • struct tm* gmtime_r(const time_t* timer, struct tm* result)
  • struct tm* localtime(const time_t* timer)
  • time_t mktime(struct tm* ptm)
  • time_t time(time_t* timer)
  • char * strptime(const char* buf, const char* format, struct tm* tptr)
  • time_t timegm(struct tm *brokentime)
float.h setjmp.h stdio.h
  • (constants)
  • int setjmp(jmp_buf env)
  • void longjmp(jmp_buf env, int value)
  • fclose
  • fopen, freopen
  • remove
  • rename
  • rewind
  • tmpfile
  • clearerr
  • feof, ferror
  • fflush
  • fgetpos, fsetpos
  • fgetc, fputc
  • fgets, fputs
  • ftell, fseek
  • fread, fwrite
  • getc, putc
  • getchar, putchar, fputchar
  • gets, puts
  • printf, vprintf
  • fprintf, vfprintf
  • sprintf, snprintf, vsprintf, vsnprintf
  • perror
  • scanf, vscanf
  • fscanf, vfscanf
  • sscanf, vsscanf
  • setbuf, setvbuf
  • tmpnam
  • ungetc

Variable-length argument lists

Functions with variable-length argument lists are functions that can take a varying number of arguments. An example in the C standard library is the printf function, which can take any number of arguments depending on how the programmer wants to use it.

C programmers rarely find the need to write new functions with variable-length arguments. If they want to pass a bunch of things to a function, they typically define a structure to hold all those things -- perhaps a linked list, or an array -- and call that function with the data in the arguments.

However, you may occasionally find the need to write a new function that supports a variable-length argument list. To create a function that can accept a variable-length argument list, you must first include the standard library header stdarg.h. Next, declare the function as you would normally. Next, add as the last argument an ellipsis ("..."). This indicates to the compiler that a variable list of arguments is to follow. For example, the following function declaration is for a function that returns the average of a list of numbers:

  float average (int n_args, ...);

Note that because of the way variable-length arguments work, we must somehow, in the arguments, specify the number of elements in the variable-length part of the arguments. In the average function here, it's done through an argument called n_args. In the printf function, it's done with the format codes that you specify in that first string in the arguments you provide.

Now that the function has been declared as using variable-length arguments, we must next write the code that does the actual work in the function. To access the numbers stored in the variable-length argument list for our average function, we must first declare a variable for the list itself:

  va_list myList;

The va_list type is a type declared in the stdarg.h header that basically allows you to keep track of your list. To start actually using myList, however, we must first assign it a value. After all, simply declaring it by itself wouldn't do anything. To do this, we must call va_start, which is actually a macro defined in stdarg.h. In the arguments to va_start, you must provide the va_list variable you plan on using, as well as the name of the last variable appearing before the ellipsis in your function declaration:

#include <stdarg.h>
float average (int n_args, ...)
{
    va_list myList;
    va_start (myList, n_args);
    va_end (myList);
}

Now that myList has been prepped for usage, we can finally start accessing the variables stored in it. To do so, use the va_arg macro, which pops off the next argument on the list. In the arguments to va_arg, provide the va_list variable you're using, as well as the primitive data type (e.g. int, char) that the variable you're accessing should be:

#include <stdarg.h>
float average (int n_args, ...)
{
    va_list myList;
    va_start (myList, n_args);
    
    int myNumber = va_arg (myList, int);
    va_end (myList);
}

By popping n_args integers off of the variable-length argument list, we can manage to find the average of the numbers:

#include <stdarg.h>
float average (int n_args, ...)
{
    va_list myList;
    va_start (myList, n_args);
    
    int numbersAdded = 0;
    int sum = 0;
     
    while (numbersAdded < n_args) {
        int number = va_arg (myList, int); // Get next number from list
        sum += number;
        numbersAdded += 1;
    }
    va_end (myList);
     
    float avg = (float)(sum) / (float)(numbersAdded); // Find the average
    return avg;
}

By calling average (2, 10, 20), we get the average of 10 and 20, which is 15.

This article is issued from Wikibooks. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.