Phase 1 · C FoundationsModule 6~46 min read

Functions, Scope & Program Structure

Break programs into reusable functions with prototypes, parameters, return values, scope, storage duration, and recursion.

What you'll learn

Functions turn a growing sequence of statements into a program with names, boundaries, and reusable ideas. This lesson covers the full function contract, how values cross that boundary, where variables are visible, and how calls occupy the stack.

By the end, you'll be able to:

  • Declare, define, call, and test focused functions
  • Use prototypes so the compiler can check calls before definitions appear
  • Explain C's pass-by-value rule
  • Distinguish scope from storage duration
  • Write a terminating recursive function and recognize its costs

Function anatomy

A function has a return type, a name, a parameter list, and—when it is a definition—a body. The signature describes how callers supply input and what they receive back.

Four parts of a function definition
double

return type

average

function name

(double total, int count)

parameters

{ return total / count; }

function body

functions.c
#include <stdio.h>

double rectangle_area(double width, double height) {
    return width * height;
}

void print_area(double area) {
    printf("Area: %.2f\n", area);
}

int main(void) {
    double area = rectangle_area(4.5, 3.0);
    print_area(area);
    return 0;
}

Tip

Use void inside an empty parameter list: void show_menu(void). In C, an old-style empty list show_menu() does not provide the same “takes no arguments” prototype.

Prototypes

A declaration tells the compiler a function's name and type. A definition additionally supplies the body. A prototype before main lets C verify the call even when the definition appears later.

prototype.c
#include <stdio.h>

double celsius_to_fahrenheit(double celsius); // declaration

int main(void) {
    printf("%.1f F\n", celsius_to_fahrenheit(20.0));
    return 0;
}

double celsius_to_fahrenheit(double celsius) { // definition
    return celsius * 9.0 / 5.0 + 32.0;
}

Key idea

In a multi-file program, public prototypes live in a header and definitions live in a source file. Writing correct declarations now prepares you for modular C.

Parameters & return values

Parameters are local variables initialized from the caller's arguments. A function may return one value directly. Small predicate functions conventionally return booland often begin with names such as is_, has_, or can_.

interfaces.c
#include <stdbool.h>
#include <stdio.h>

bool is_in_range(int value, int minimum, int maximum) {
    return value >= minimum && value <= maximum;
}

int clamp(int value, int minimum, int maximum) {
    if (value < minimum) return minimum;
    if (value > maximum) return maximum;
    return value;
}

int main(void) {
    printf("%s\n", is_in_range(14, 1, 10) ? "inside" : "outside");
    printf("%d\n", clamp(14, 1, 10));
    return 0;
}
FormMeaning
int next(int value)Accept an int and return an int
void print_report(int id)Accept an int and return no value
int read_choice(void)Accept no arguments and return an int
bool is_valid(int value)Return a Boolean result

Pass-by-value

C passes arguments by value: each parameter receives a copy. Reassigning that parameter cannot reassign the caller's object.

pass_by_value.c
#include <stdio.h>

void try_to_change(int value) {
    value = 99;                 // changes only this function's copy
    printf("Inside: %d\n", value);
}

int main(void) {
    int score = 10;
    try_to_change(score);
    printf("Caller: %d\n", score);
    return 0;
}

Note

Later, pointer parameters will let a function access caller-owned objects by address. The pointer itself is still passed by value—the copied address simply refers to shared storage.

Scope & storage duration

Scope answers where a name is visible. Storage durationanswers how long its object exists. A local automatic object normally exists for one call; a static object exists for the entire program.

scope.c
#include <stdio.h>

static int file_total = 0;     // file scope, internal linkage

void record_visit(void) {
    static int calls = 0;      // block scope, static storage duration
    int this_visit = 1;        // block scope, automatic storage duration

    calls++;
    file_total += this_visit;
    printf("call=%d total=%d\n", calls, file_total);
}

int main(void) {
    record_visit();
    record_visit();
    return 0;
}
DeclarationName visibilityObject lifetime
Local automaticIts enclosing blockFrom block entry until exit
Local staticIts enclosing blockEntire program
File-scope staticRemaining translation unitEntire program
File-scope externalCan be declared in other translation unitsEntire program

Minimize shared state

File-scope mutable objects make functions harder to test and reason about. Prefer local state and explicit parameters unless shared lifetime is genuinely part of the design.

The call stack & recursion

Each active function call has a stack frame containing bookkeeping and its automatic local objects. A recursive function calls itself with a smaller problem and must contain a base case that stops further calls.

recursion.c
#include <stdio.h>

unsigned long long factorial(unsigned int n) {
    if (n <= 1U) {             // base case
        return 1U;
    }
    return n * factorial(n - 1U); // recursive case
}

int main(void) {
    printf("%llu\n", factorial(5));
    return 0;
}
How factorial(3) unwinds

top of stack

factorial(1)returns 1
factorial(2)2 × 1
factorial(3)3 × 2
main()prints 6
The newest call sits at the top; returns flow back toward main.

Watch out

Recursion consumes one stack frame per active call. Missing a base case eventually exhausts the stack; very deep recursion may be unsuitable even when logically correct.

Clean function design

Functions become easier to reuse and test when they follow a few disciplined rules:

  • Give each function one responsibility and a verb-based name
  • Keep the interface small and make invalid cases explicit
  • Prefer returned results over hidden mutation of global state
  • Use early returns for guards when they reduce nesting
  • Separate calculation from input/output when practical
clean_functions.c
#include <stdbool.h>

bool is_valid_percentage(int value) {
    return value >= 0 && value <= 100;
}

double percentage(int earned, int possible) {
    if (possible <= 0) {
        return 0.0;
    }
    return (double) earned / possible * 100.0;
}

Tip

percentage performs a calculation without reading or printing. That makes it easy to call from a terminal program today and a graphical or embedded program later.

Recap & quick check

Key takeaways

  • A function declaration introduces its type; a definition also provides its body.
  • Use (void) to declare a C function that takes no arguments.
  • C passes every argument by value, so a parameter begins as a copy.
  • Scope controls name visibility, while storage duration controls object lifetime.
  • Recursion requires a base case and consumes stack space for every active call.

Quick check

1. What does a function prototype let the compiler check?

2. After a function assigns 99 to an int parameter, what happens to the caller's int?

3. Which object preserves its value between calls?

4. What must every terminating recursive function have?

Phase 1 complete. You can compile C, model data, communicate, choose, repeat, and organize logic into functions. Phase 2 begins with Module 7 — Arrays, where multiple values occupy one contiguous region of memory.