Functions in C

October 7, 2026 • 6 min read

Functions in C
Table of contents

Part of the series:C Explained

A function is a named block of code you can call from anywhere else. It takes inputs, returns an output, and lets you write a piece of logic once and reuse it. main is itself a function, so you have been writing functions since the first post; this one teaches the full picture.

Anatomy of a function

A function definition has a return type, a name, a parameter list in parentheses, and a body in braces:

int add(int a, int b) {
    return a + b;
}

Read it as: “function add takes two ints, returns an int, and its body returns their sum.” To use it, call it with matching arguments:

int result = add(3, 4);   // result is 7

The pieces, in order:

  • int - the type of the value returned. A function that returns nothing uses void.
  • add - the identifier.
  • (int a, int b) - parameters: the inputs, each a type plus a name. A function with no inputs writes void inside: int main(void).
  • { return a + b; } - the body, with return sending a value back to the caller.
  • The caller’s values are arguments; the function’s names for them are parameters. The two often use different names; only positions matter.

Prototypes: declaring before using

In C you cannot call a function before the compiler has seen its definition, because the compiler needs to know its signature to type-check the call. If add is defined after main, this fails:

int main(void) {
    int r = add(3, 4);    // error: 'add' was not declared
    return 0;
}

int add(int a, int b) {
    return a + b;
}

The fix is a prototype (also called forward declaration): the signature with a ; instead of a body, placed before the call. Real programs put these in header files, but for now, top of the file works:

int add(int a, int b);    // prototype

int main(void) {
    int r = add(3, 4);
    return 0;
}

int add(int a, int b) {   // definition
    return a + b;
}

In a strict C99 project the function must be declared before every use, which is why headers exist: the prototype lives in a .h file shared by every .c file that calls the function.

Pass by value

C passes arguments by value: the parameter is a copy of the argument. Changes inside the function never touch the caller’s variable:

void change(int x) {
    x = 99;
}

int main(void) {
    int n = 5;
    change(n);
    printf("%d\n", n);   // still 5
    return 0;
}

This is the single most important rule about functions in C, and it explains why the next post’s arrays and the pointers post’s & operator are indispensable: to let a function modify a caller’s variable, you must pass a pointer to it, because a copy of an address still names the original.

return and void

return exits the function and hands back a value of the declared return type:

double average(double a, double b) {
    return (a + b) / 2.0;
}

Ending the function without a return when the type is not void is undefined behavior in most cases; the compiler may help you catch it only sometimes. void functions return no value; use a bare return; to leave early, or none at all:

void greet(const char *name) {
    if (name == NULL) {   // guard clause
        return;
    }
    printf("Hello, %s\n", name);
}

Scope and lifetime

Where a variable exists is its scope, and how long it survives is its lifetime. The blocks from the syntax post are the boundaries:

  • A variable declared inside a function is local: visible only inside its block, and destroyed when the block ends.
  • The parameters are local to the function body.
  • A variable declared outside any function is global: visible everywhere after its declaration, and alive for the whole program.
  • A block {} can open its own scope for a temporary variable.
int global = 10;                 // global, lives forever

void f(void) {
    int local = 5;               // local to f
    {
        int temp = local + 1;    // block scope: only inside these braces
    }
    // temp does not exist here
}

Prefer local variables. Globals make functions hard to reason about, because any part of the program can change them.

Storage classes

Four keywords control storage class: where a variable lives and how long it lasts.

  • auto - the default for locals; you practically never write it.
  • register - a hint to keep the value in a CPU register; the compiler largely ignores it today.
  • static - on a local: the variable keeps its value between calls (and is initialized only once). On a global or function: it becomes visible only within its own file, hiding it from other files.
  • extern - declares that the variable or function is defined in another file, letting you use it across files.

The static local is the useful one:

int nextId(void) {
    static int id = 1000;   // keeps its value across calls
    return id++;
}

Each call returns 1000, then 1001, and so on, because the variable survives between calls even though it is local. const (from the syntax post) is not a storage class but is often grouped with these: it marks a value read-only.

Recursion

A function can call itself; that is recursion1. Its natural home is problems that break into smaller versions of themselves. The classic is factorial: n! = n * (n - 1)!, with 0! = 1:

int factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}
factorial(4)
  -> 4 * factorial(3)
    -> 3 * factorial(2)
      -> 2 * factorial(1)
        -> 1

Every recursive function needs a base case (the n <= 1 branch) that stops the chain; without it, the calls never end and the program crashes with a stack overflow (the recursion story connects to the pointers/memory post later). Compared to loops, recursion shines on nested data like trees; use a loop for plain counting.

The big picture

A function is a named, reusable block: a return type, parameters passed by value, a body, and return to send results back. Prototypes let later definitions be called earlier; void means “returns nothing”; scope rules decide what each name means; storage classes tune lifetime and visibility; and recursion handles problems that fold into themselves. Functions combine with conditionals and loops to express any logic, and the next post applies them to the first built-in data structure: arrays and strings.

Footnotes

  1. Recursion (computer science) - Wikipedia