Functions are reusable code blocks that will only execute when called. They allow developers to divide their programs into smaller parts that are easier to understand and reuse.
Defining Functions
A
function can be created by typing
void followed by the function’s name, a set of parentheses containing another
void, and a code block. The first use of the
void keyword specifies that this function will not return a value. The second
void inside the parentheses means that the function does not accept any arguments.
void myFunction(void) {
printf("Hello World");
}
Calling Functions
The previous
function will print out a text message when it is called. To invoke it, the function’s name is specified followed by an empty set of parentheses.
int main(void) {
myFunction(); /* "Hello World" */
}
Function Parameters
The
parentheses that follow the function’s name are used for passing arguments to the function. To do this, you must first add the corresponding parameters to the function’s parameter list.
void sum(int a, int b) {
int sum = a + b;
printf("%d", sum);
}
A function can be defined to take any number of arguments, and they can have any data types. Just ensure the function is called with the same types and number of arguments. In this example, the function accepts two integer arguments and displays their sum.
To be precise, parameters appear in function definitions, while arguments appear in function calls. However, the two terms are sometimes used interchangeably.
Void Parameter
In C,
functions that leave out the
void keyword from their parameter list are allowed to accept an unknown number of arguments. This is different from C++, where leaving out
void means the same as including it: that the function takes no arguments. Therefore, to have the compiler ensure that no arguments are mistakenly passed to a parameterless function, it is necessary in C to include
void in the parameter list.
/* Accepts no arguments */
void foo(void) {}
/* Accepts an unknown number of arguments */
void bar() {}
As of C99, the use of an empty parameter list has been deprecated and results in a warning from the compiler.
Return Statement
A
function can return a value. The
void keyword before the function’s name is then replaced with the data type the function will return, and the
return keyword is added to the function’s body followed by an argument of the specified return type.
int getSum(int a, int b) {
return a + b;
}
Return is a jump statement that causes the function to exit and return the specified value to the place where the function was called. To illustrate, the previous function can be passed as an argument to the
printf function since it evaluates to an integer.
printf("%d", getSum(5, 10)); /* "15" */
The
return statement can also be used in a void function as a way to exit the function before the end block is reached.
void dummy(void) { return; }
The
main function must be set to return an
int type, but including an explicit return value is only required in the C89 standard. As of C90 the compiler will automatically add a
return statement to the end of the
main function if no such statement is present, and with the C99 standard, this implicit return value is guaranteed to be
zero.
int main(void) {
return 0; /* optional */
}
Forward Declaration
An important thing to keep in mind in C is that a function must be declared before it can be called. This can either be achieved by placing the function’s implementation before any references to it, or by adding a declaration of the function before it is called. This kind of
forward declaration is known as a
prototype and provides the compiler with the information needed to allow the function to be used before it has been defined.
void myFunction(int a); /* prototype */
int main(void) {
myFunction(0);
}
void myFunction(int a) {}
The parameter names do not need to be included in the prototype; only the data types are required.
In early versions of C, an undeclared function that is referenced is implicitly declared as a function that returns an
int and takes an unspecified number of parameters. Relying on this behavior is not recommended and usually results in a warning from the compiler. As of C99 this feature has been removed and will instead result in an error.
/* Warning: implicit declaration of foo */
int foo() { return 0; }
Variable Parameter Lists
A function can be defined to accept a variable number of arguments, similar to the printf function. The parameter list of such a function must end with an ellipsis (…) and there must be at least one additional parameter. An int parameter is typically included to let the function know the number of extra arguments that are passed to it.
In the following example, the function accepts a variable number of arguments that are summed up and returned to the caller. To access these arguments the
stdarg.h header file is included. This header defines a new type, called
va_list, and three functions that operate on
variables of this type:
va_start,
va_arg, and
va_end.
#include <stdio.h>
#include <stdarg.h>
int sum(int num, ...) {
va_list args; /* variable argument list */
int sum = 0, i = 0;
va_start(args, num); /* initialize argument list */
for (i = 0; i < num; i++) /* loop through arguments */
sum += va_arg(args, int); /* get next argument */
va_end(args); /* free memory */
return sum;
}
int main(void) {
printf("Sum of 1+2+3 = %d", sum(3,1,2,3)); /* 6 */
}
In contrast to C++, C does not allow function overloading or default parameter values. However, variable parameter lists can be used to implement functions that behave in similar ways.
Pass by Value
Variables are by default passed by
value. This means that only a copy of the value is passed to the function. Therefore, changing the parameter in any way will not affect the original variable, and passing large variables back and forth can have a negative impact on performance.
void set(int i) { i = 1; }
int main(void) {
int x = 0;
set(x);
printf("%d", x); /* "0" */
}
Pass by Address
The alternative to passing by
value is to use pointer syntax to instead pass the variable by address. When an argument is passed by address, the parameter can be changed or replaced, and the change will affect the original variable.
void set(int* i) { *i = 1; }
int main(void) {
int x = 0;
set(&x);
printf("%d", x); /* "1" */
}
Recall that arrays can be treated as pointers. As such they will automatically be passed by address, as shown in the following example.
void set(int a[]) { a[0] = 1; }
int main(void) {
int x[] = { 0 };
set(x);
printf("%d", x[0]); /* "1" */
}
Return by Value or Address
In addition to passing
variables by value or address, a variable may also be returned in one of these two ways. By default a function returns by value, in which case a copy of the value is returned to the caller.
int byVal(int i) { return i + 1; }
int main(void) {
int a = 10;
printf("%d", byVal(a)); /* "11" */
}
To instead return by address, the dereference operator is appended to the function’s return type. The function must then return a variable and not an expression or literal, as is allowed when returning by value. The variable returned should never be a local variable since the memory to these variables is released when the function ends. Instead, return by address is commonly used to return an argument that has also been passed to the function by
address.
int* byAdr(int* i) { (*i)++; return i; }
int main(void) {
int a = 10;
int *p = byAdr(&a);
printf("%d", *p); /* "11" */
}
Inline Functions
When
calling a function it is important to keep in mind that a certain performance overhead occurs. To potentially remove this overhead, the programmer can recommend that the compiler inlines the calls to a specific function by using the
inline function modifier. This keyword was added in the C99 standard. It is most suited for use with small functions that are called inside loops, as shown in the following example. Larger functions should not be inlined since this can significantly increase the size of the code, which may instead decrease performance.
inline int increment(int a) { return ++a; }
int main(void) {
int i;
for(i = 0; i < 100;) {
i = increment(i);
}
}
Note that the inline keyword is only a recommendation. The compiler may—in its attempts to optimize the code—choose to ignore this recommendation, and it may also inline functions that do not have the inline modifier
.