CBE Pt.9: Functions in C
Local Variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <stdio.h>
// for: int printf(const char *, ...);
void
foo(void)
{
int a = 42;
int b = 123;
printf("foo: a = %d, b = %d\n", a, b);
}
void
bar(void)
{
int x;
int y;
printf("bar: x = %d, y = %d\n", x, y);
}
int
main(void)
{
foo();
bar();
}
|
theon$ gcc -o local local.c theon$ ./local foo: a = 42, b = 123 bar: x = 42, y = 123 theon$
Function Parameters: Call by Value
Call by value means: If a function gets parameters it always gets a copy of them. In this example function foo changes its parameters. Because this is just a copy it has no effect to original variable in function main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h>
// for: int printf(const char *, ...);
void
foo(int a)
{
a = 123;
}
int
main(void)
{
int a = 42;
printf("before calling foo(): a = %d\n", a);
foo(a);
printf("after calling foo(): a = %d\n", a);
}
|
theon$ gcc -o param param.c theon$ param before calling foo(): a = 42 after calling foo(): a = 42 theon$
How to Mimic “Call by Reference”
Other programming languages, e.g. Fortran, use the call by reference concept. If you pass a variable to a function as parameter you are giving away the original variable. So if this function makes changes to it you would afterwards see the effect.
If you want this behavior in C then you have to pass a pointer. The function will get a copy of this pointer but this copy still points to the same memory location. So this is how the above example can be changed to mimic the call by reference in C:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h>
// for: int printf(const char *, ...);
void
foo(int *a)
{
*a = 123;
}
int
main(void)
{
int a = 42;
printf("before calling foo(): a = %d\n", a);
foo(&a);
printf("after calling foo(): a = %d\n", a);
}
|
theon$ gcc -o param2 param2.c theon$ param2 before calling foo(): a = 42 after calling foo(): a = 123 theon$
Recursive Function Calls
The following program computes the factorial recursively. That means function factorial calls in some cases itself, but with a different parameter. Now first have a look at the code. Below you will find a graphic showing the stach frames when factorial gets called with n equal to 3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h>
// for: int printf(const char *, ...);
unsigned long long
factorial(unsigned long long n)
{
return n <= 1 ? 1 : n * factorial(n - 1);
}
int
main(void)
{
// 20! can be represented with 64 bits but 21! can not.
for (unsigned long long n = 0; n < 21; ++n) {
printf("%2llu! = %llu\n", n, factorial(n));
}
}
|
Assume we are in function main before a call of factorial(3) takes place. And the stack looks like that (everything on the right-hand side of the stack pointer is somehow in use):
Right after the call of factorial(3) we therefore have a stack frame for this call instance:
Because n <= 1 is in this instance false there will be a call of factorial(2). Right after this call we have another stack frame. This time for the function call instance factorial(2):
In this instance n <= 1 is also false. So there will be another call, hence another instance, this time for factorial(1):
In this instance n <= 1 is true. So it will return 1 and the stack frame gets “cleared”:
So factorial(2) now return 2 (= 2 * factorial(1)) and right after that we have
So factorial(3) will return 6 (= 3 * factorial(2)) and we have the stack pointer at the same position as when we started:
theon$ gcc -o factorial factorial.c theon$ factorial 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800 11! = 39916800 12! = 479001600 13! = 6227020800 14! = 87178291200 15! = 1307674368000 16! = 20922789888000 17! = 355687428096000 18! = 6402373705728000 19! = 121645100408832000 20! = 2432902008176640000 theon$