Logo  

CS351 - Computer Organization

Reading:

Functions

A function, procedure or sub-routine are all names for a self-contained section of code, like a mini-program with optionally it's own inputs and outputs. Functions are useful for encapsulating code that can be called from multiple locations in the same program (so that it does not need to be re-written in multiple places). They can also be for structurally laying out of a program (via top down development,) or just to reduce the cognitive load with respect to some code -- one can stop thinking about what the individual statements in the function are doing and just think in terms of the whole function. It allows ones focus to zoom out of the code and keep more of the program in ones mind while programming. In general it is a good practice to use functions more often and there is rarely any downside.

Grammar for a function definition:

return-type name ( variable-declarations )
{
    statements;
    ...
}

The purpose of a function definition is to define completely the function, it's inputs (the parameters to the function), output(s) (given by the return type) and the internal code that processes the inputs (the statements within the {}'s).

Grammar for a function prototype:

return-type name ( variable-declarations );

The purpose of a prototype is define a function defined either in another file or below the function attempting to use that function (in which case the prototype should be defined above the function attempting to use the function.) It contains just enough information to inform the compiler of how the function should be used (i.e. its inputs and outputs.)

Return

The return keyword causes a function to exit immediately, giving a function it's value if a function has a non-void return type.

The return keyword has the following grammar:

return [expression];

The expression can be omitted for functions with a void return type (i.e. functions that return no value, see below for more information.) Otherwise the expression must evaluate to the same kind of data that the function is expected to return (although some data-types can be converted to other data types, such as integer to floating point and visa-versa, this would still usually need to be done explicitly with a type-cast (to be discussed later.)

Examples:

// Return the lesser of a or b:
if (a <= b) return a;
else return b;

// Return from a void function:
return;

// Return the result of a "complicated" expression:
return a+b*c/2;

The 'void' Type

The void keyword defines a non-data type. Mostly used for functions to define functions that either return no data and/or take no data. If the return type for a function is void, it is unecessary to use return by the end of the function as no data needs to be returned. If a function requires no parameters it is a good idea to use void where the variable-declarations would normally go to indicate to the compiler or anyone reading the code that no parameters are required.

Example:

// Function that takes no values and returns nothing:
void foo(void) {
  ...
  // Note: It is not necessary to use return; at the end of void functions.
  return;
}

Function Parameters

A functions parameters are defined as a comma separated list of full variable declarations (including the type) one for each variable, thus if two integer variables are required, they should be specified separately. Thus:

int foo(int a, int b)

In this example both the a and b variables are parameters or arguments to the function and act as local variables in the function (see Variable Scope below.) They are also inputs to the function, they are the means by which data is sent to the function by the caller.

More Examples:

// Function that takes an integer (a), a double (b) and a character (c)
// and returns a double as it's value:
double foo(int a, double b, char c) {
  ...
}

// Function that returns no value, and takes three integers:
void bar(int a, int b, int c) {
  ...
}

"Calling" a Function

Once a function is "defined" or "prototyped", you can use it elsewhere in your program by using the name of the function followed by the data you want to send to it inside of parenthesis (().) Normally you need to give the function as many parameters in the call as it is defined to need. Each parameter you pass to the function is stored in the parameters, moving from left to right in the definition. Thus:

void foo(int a, int b, int c) {
 ...         │      │      │
}            └───┐  │      │
                 │  │      │
void bar(void) { │  │      │
  foo(1, 2, 3);  │  │      │
  ... │  │  └────│──│──────┘
}     │  └───────│──┘
      └──────────┘


Thus when foo in invoked in the bar() function, the value 1 will be placed in a, 2 in b and 3 in c respectively, then the code in the function is executed. Once the code in foo() is done (either from a return or it reaches the end of the function,) execution will continue in bar() from the statement that follows the call to foo().

Call By Value

By default when a function is called, any character, integer or floating point values (and the not-yet discussed structures) used as parameters to the function are passed by value, i.e. the value is copied to the functions parameter variable. Thus if you use such variables in the call, that variable is not modified by the calling function.

int v = 1;

foo(v, 2, 3);

In the above, the value is sent to foo, but not any reference to the variable, thus if foo modifies it's local variable a, then v in the calling function will remain the same value in the calling function. Remember that "call by value" means data is copied to the function, thus the value in v is just copied to foo's a parameter, much like in assignment (i.e. a = v;).

Call By Reference

We use call by reference when using the scanf() function. Call by reference sends the address (it's location in memory) of a variable to the function so that the called function can modify the variable in question. With the address (or reference) to the variable, the function can both read and modify the variable used in the function call.

Strings (character arrays) and arrays of other data are often passed by reference (address). To make a function that will take integer or float references, we would need to discuss pointers, which is a topic for a later time, but for now understand that you pass a non-pointer variable by reference using the ampersand (& - "address of") operator prefixed to a variable that needs to be passed by reference.

Again only non-array character, integer or floating point values (and structures) require the ampersand operator to make them pass by reference, arrays or strings (which are arrays of characters,) already pass by reference by default.

Example:

int d;
float f;
char c;

// Without a reference to these variables, scanf() could not modify them.
scanf("%d %f %c", &d, &f, &c);

Visibility of Functions In A Program

The word "visibility", when used in the context of a programming language, describes what is known about in other areas of a program. Usually we care mostly about variable visibility (or scope) since we may want to reuse the names of variables in different functions, but functions themselves have different visibility depending on where they are defined.

If a function is defined in one file, then it is not visible to function in a different file, unless a prototype for the function is defined above the function that attempts to use it. Also if a function is defined after/below a function that would like to use it, then a prototype for the function must be made above the function that wants to use the function. If a function is defined before/above a function

Examples:

File 1 File 2
int foo();
{
  foo stuff;
}
int foo();
int baz();

int bar()
{
   foo();
   baz();
}
int baz()
{
  baz stuff;
}

In the above example if the function baz() were to be defined above bar(), then there would be no need for the prototype of it, as bar() could "see" it, i.e. baz() would be visible to it.

Variable Scope

Variable scope refers to the "visibility" of a variable within the program. The various scopes are:

global

  • A variable that is declared outside of any function and can be seen by any function within the same file. To access a global from a different file or from a function in a different file use "extern declaration;" to define the variable found within another file.

    extern int foo;

    • Allows us to use the variable foo found in a different source file.

static global

  • A global declared with the static modifier is only visible to the file and cannot be accessed outside of the file (not even with extern.)

local

  • Variables defined in a function are only visible within the function they are defined. A functions parameters also have local scope.

block-local

  • Variables defined within a block are only visible within the block (and any sub-blocks) they are defined in. All local variables are really block-local.

static local

  • A local variable declared with the static modifier does not lose its value between function calls (think of it as a global variable that is visible only to the function in which it is defined.)
#include <stdio.h>

int this_is_global;
static int this_is_global_and_only_visible_to_this_file;

int function(int this_is_local)
{
  int this_is_also_local;
  static int this_is_local_but_keeps_it_value_between_calls;

  if (x) {
    int this_is_block_local;
  }
}

Review

Things you should definately know:

  • Difference between function definition and prototypes & grammar.
  • Visibility of functions in a program.
  • The return keyword.
  • The void type.
  • How function parameters are defined and how they are filled with values during function calls.
  • Call by value vs. reference
  • Scope: global & local