C

Functions

A function is a named, reusable block of code that takes some inputs, does something with them, and optionally hands a result back. Every C program you've seen in this domain already has one — main — this chapter is about writing your own, and understanding exactly what happens in memory every time one is called.

Quick reference
What it is
A named block of code, with its own parameters and (optionally) a return value, that can be called from anywhere it's visible.
Mental model
A small, self-contained machine: you feed it inputs (parameters), it does work, and it either hands back one result (return value) or nothing at all (void).
Key numbers
Every function call adds one new "stack frame" to the call stack; that frame is freed the instant the function returns.
Reach for it when
The same logic is needed in more than one place, or a block of code is complex enough that giving it a name makes the calling code easier to read.

Anatomy of a function

int add(int a, int b) {
    int sum = a + b;
    return sum;
}
int (first one)
The return type — the type of value this function hands back to its caller. void means it returns nothing.
add
The function's name — how it's called elsewhere in the program.
(int a, int b)
The parameter list — the inputs this function accepts, each with its own type and name, local to this function only.
return sum;
Ends the function immediately and hands sum back to whoever called it.
int main(void) {
    int result = add(3, 4);   // "call" add with arguments 3 and 4
    printf("%d\n", result);   // 7
    return 0;
}

Declaration vs. definition

A function can be declared (just its signature, ending in a semicolon, promising "this exists") separately from where it's defined (the actual body). This matters because C reads top to bottom — a function has to be declared before it's used, so a declaration up top lets you call it before its full definition appears later in the file:

int add(int a, int b);   // declaration ("prototype") — no body

int main(void) {
    printf("%d\n", add(3, 4));   // legal — the compiler already knows add's signature
    return 0;
}

int add(int a, int b) {   // definition — the actual body
    return a + b;
}

This is exactly why header files (.h) exist — they hold declarations so multiple .c files can call each other's functions, with the actual definitions living in exactly one .c file. See Embedded C's Compilation & Linking for the full mechanism.

Parameters and return values

C passes arguments by value by default — a function receives its own private copy of each argument, not the original variable. Changing a parameter inside the function has zero effect on the caller's variable:

void try_to_change(int x) {
    x = 100;   // only changes this function's own local copy
}

int main(void) {
    int n = 5;
    try_to_change(n);
    printf("%d\n", n);   // still 5 — n was never actually touched
    return 0;
}

Actually modifying a caller's variable from inside a function requires passing its address instead of its value — the subject of the next chapter, Pointers.

The call stack

Every function call reserves a small block of stack memory — a stack frame — holding its parameters and local variables. Calling another function from inside one pushes a new frame on top; returning pops it back off:

main calls add, which pushes a new stack frame on top of main's own frame; when add returns, its frame is popped and control returns to main main()'s frame result, n, ... add()'s frame a=3, b=4, sum=7 pushed on call, popped on return add(3, 4) is called from inside main() Stack grows upward in this diagram — see Embedded C's Storage Classes for the full memory map.
This is why a variable local to add doesn't exist before it's called and stops existing the instant it returns — its entire storage is that stack frame.

Recursion

A function that calls itself is recursive. Each call gets its own independent stack frame, so each "copy" of the function has its own separate parameters and locals:

int factorial(int n) {
    if (n <= 1) {
        return 1;              // base case — stops the recursion
    }
    return n * factorial(n - 1);   // recursive case
}
// factorial(4) → 4 * factorial(3) → 4 * 3 * factorial(2) → 4 * 3 * 2 * factorial(1) → 4 * 3 * 2 * 1 = 24

Every recursive function needs a base case — a condition that stops the recursion without calling itself again. Without one, it calls itself forever, pushing a new stack frame every time, until it runs out of stack memory entirely (a stack overflow).

Real-world applications

Gotchas

Field notes