Skip to content

Chapter 8 of 21

Functions

Design declarations, definitions, parameters, return values, and recursion.

30 minutes 10 quick checksBy Subha Prasad
Lesson 8 of 21Course navigation

Lesson content

Read, practise, then check your understanding

Functions divide a program into named operations with explicit inputs and outputs. A good function has one coherent responsibility, a documented contract, and no hidden dependency that could be passed as a parameter.

Declaration and definition

A declaration makes the interface known; a definition supplies the body.

double average(const int values[], size_t count); // declaration

double average(const int values[], size_t count) { // definition
    if (count == 0) return 0.0;
    long long total = 0;
    for (size_t i = 0; i < count; ++i) total += values[i];
    return (double)total / (double)count;
}

Put public declarations in headers and definitions in source files. Include the function’s own header in its implementation so the compiler detects mismatches.

C passes by value

Every argument is passed by value. To let a function modify a caller’s object, pass its address.

bool divide(int numerator, int denominator, double *result) {
    if (result == NULL || denominator == 0) return false;
    *result = (double)numerator / denominator;
    return true;
}

This pattern returns status separately from output. Document whether pointer parameters may be null and who owns pointed-to memory. Array parameters adjust to pointers, so pass a length alongside them.

Scope and private helpers

A function defined with static at file scope has internal linkage; only that translation unit can name it. This is useful for private helpers and lets the compiler optimize with more context.

Variadic functions

Functions such as printf accept a variable argument list. Custom variadic functions use <stdarg.h>, but extra arguments carry limited type information. Prefer an array, structure, or explicit interface when practical.

Recursion

A recursive function calls itself on a smaller problem and must have a base case.

unsigned long long factorial(unsigned n) {
    if (n < 2) return 1;
    return n * factorial(n - 1);
}

Recursion consumes stack space and can overflow for deep input. Iteration is often more robust for linear tasks. Recursion is natural for trees and divide-and-conquer algorithms when depth is controlled.

Function design checklist

  • Validate inputs at the boundary.
  • Keep parameter lists understandable; group related configuration in a struct.
  • Return a documented status or value on every path.
  • Keep functions short enough that invariants remain visible.
  • Avoid global mutable state when data can be passed explicitly.

Knowledge check

Answer every question correctly to complete this chapter.

What gives the compiler a function’s name, return type, and parameter types before use?
What must every useful recursive solution contain?
How does C pass ordinary function arguments?
How can a function modify a caller’s int object?
What does static on a file-scope function provide?
Why should an array parameter usually be paired with a length?
What does const int *values promise through that parameter?
Which header supports variadic function traversal?
What is a risk of unbounded recursion?
Why include a public function’s own header in its implementation file?

0 of 10 checks passed

Your progress is saved on this device.

Functions | C Lesson | Subha Prasad