A function is a named block of code. Gathering the same tasks in one place Duplication is reduced, and only one place needs to be modified when changing.
Learning Objectives
- Know the difference between function declaration (prototype), definition, and call.
- Distinguish between parameter passing methods (value passing, pointer passing).
- Design termination conditions for recursive functions.
- Understand the lifespan and visibility of the
staticvariable.
Why split it into functions?
If you put all the code into one long main:
- Duplicate code increases
- Difficult to find where to fix a single bug
- Difficult to reuse code
If you break it down into functions, like “This function calculates the average” or “This function sorts” You can handle code on a per-role basis.
Key concepts
1) 3 types of functions
/*1) Declaration (prototype) — announced above main*/
int add(int a, int b);
/*2) Definition — actual text*/
int add(int a, int b) { return a + b; }
/*3) Call*/
int s = add(3, 4);
Parameter data types, return types, and names must all match.
2) Flow of call stack
int add(int a, int b) { return a + b; }
int main(void) {
int s = add(3, 4); //← When main calls add
return 0; //main stops until add is finished
}
Start call End call
┌────────┐ ┌────────┐
│ add │ ◄── currently running │ main │ ◄── currently running
├────────┤ └────────┘
│ main │
└────────┘
Stack (Last-In-First-Out)
When the function ends, it returns to exactly where it was called.
3) C always passes by value (call by value)
void inc(int x) { x++; } //Increment the copy by 1 and end
int n = 5;
inc(n); //n remains 5
To change a variable in the caller, you must pass a pointer.
void inc_ptr(int *x) { (*x)++; }
inc_ptr(&n); //n is 6
4) Recursive function
long long factorial(int n) {
if (n <= 1) return 1; //Terminating condition (base case)
return n * factorial(n - 1); //call yourself
}
Stack overflow (infinite calls) if exit condition is missing or incorrect.
5) The two faces of static
staticvariable within a function: The value is maintained between calls (lifetime is the entire program).staticattached to the function: Can only be called within the same file (not visible to the outside).
See as example
Example 1 — ex01_basic.c: Declaration/Definition/Call in function
int add(int a, int b);
int max(int a, int b);
void print_line(void);
Execution result
--------------------
add(3, 4) = 7
max(7, 2) = 7
--------------------
Key point: the prototype is above main so the compiler knows the calling type in advance.
Example 2 — ex02_recursion.c: Factorial and Fibonacci
long long factorial(int n);
long long fib(int n);
Execution result
0! = 1, fib( 0) = 0
1! = 1, fib( 1) = 1
2! = 2, fib( 2) = 1
3! = 6, fib( 3) = 2
4! = 24, fib( 4) = 3
5! = 120, fib( 5) = 5
6! = 720, fib( 6) = 8
7! = 5040, fib( 7) = 13
8! = 40320, fib( 8) = 21
9! = 362880, fib( 9) = 34
10! = 3628800, fib(10) = 55
Key: The termination condition (n <= 1) is the leaf of the call tree.
Example 3 — ex03_array_param.c: Array to function
int sum(const int arr[], int n) { ... }
double average(const int arr[], int n) { return (double)sum(arr,n) / n; }
Execution result
Total: 150
Average: 30.00
Bottom line: Array parameters to functions are effectively pointers. So we get the length separately.
Example 4 — ex04_static.c: Maintaining values between calls
int counter(void) {
static int count = 0;
count++;
return count;
}
Execution result
call 1 -> 1
call 2 -> 2
call 3 -> 3
call 4 -> 4
call 5 -> 5
Key point: Without static, it will be initialized to 0 every time and will always output 1.
See it from a different perspective — “Function is a vending machine”
Parameter (input) Return value (output)
▼ ▲
┌───────────────────────────┐
│ Function body (black box) │
└───────────────────────────┘
A deterministic vending machine that gives the same output for the same input is easier to test and reuse. The fewer side effects, such as dependencies on global variables and screen output, the better.
Common Mistakes
- Missing return type: If you leave out the return type like
add(int a, int b), old C assumesint(warning now). - Call without declaration: The compiler may guess and use the wrong calling convention.
- Missing recursion termination condition: infinite recursion → stack overflow (runtime crash).
- Return of local variable address: Invalid after function ends (
int *bad(void) { int x; return &x; }). sizeofof array parameter:void f(int a[10]) { sizeof(a); }result is pointer size, not array size.
Cleanup
- Functions are tools for separating and reusing code by role.
- The call uses the call stack and returns to the caller when finished.
- C is for passing by value. Pass a pointer to change a variable in the caller.
- Recursion must have an exit condition.
- The
staticlocal variable maintains its value between calls.
Try it yourself
cd src
gcc -std=c11 -Wall -o ex01 ex01_basic.c && ./ex01
gcc -std=c11 -Wall -o ex02 ex02_recursion.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_array_param.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_static.c && ./ex04
Application:
- Rewrite
fibofex02as a loop version and compare the performance. - Subtract
staticfromex04and see how the call result changes.