Understanding Functions in C: From Basics to Recursion

What Exactly Is a Function?

In mathematics, a function maps enputs to outputs. In C, the idea is similar: a function is a named block of code that performs a specific task, optionally receives data, optionally returns data, and hides implementation details from the caller. Formally, it is a sub-routine that can be invoked (called) from anywhere in the program once it is declared or defined.

Two Flavors of Functions

  1. Library functions – supplied by the C standard library.
  2. User-defined functions – written by the programmer.

Library Functions

The standard library ships with ready-made utilities for common tasks such as printing, string manipulation, memory filling, mathematical calculations, and date/time handling. Typical headers include <stdio.h>, <string.h>, <math.h>, and <time.h>.

Quick examples:

#include <stdio.h>
#include <string.h>

int main(void)
{
    char buf[32] = "hello";
    strcpy(buf, "world");          /* copy */
    memset(buf, '-', 5);           /* fill */
    puts(buf);                     /* print */
    return 0;
}

Key point: every library function requires the correct header to be included; otherwise the compiler issues warnings or errors.

User-Defined Functions

Library utilities cannot cover every domain-specific requirement, so we craft our own. A user-defined function has four parts:

return_type  name(parameter_list)
{
    body
}

Example 1 – maximum of two integers

int max_int(int a, int b)
{
    return a > b ? a : b;
}

Example 2 – swap two integers

/* WRONG: pass-by-value */
void swap_fail(int x, int y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

/* RIGHT: pass-by-address */
void swap_ok(int *x, int *y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

Parameters: Actual vs. Formal

  • Actual parameters (arguments) – expressions supplied at the call site.
  • Formal parameters – variables declared in the function header.

During the call, each formal parameter is initialized with the value of the corresponding actual parameter. In pass-by-value, the formal parameter is a temporary copy; modifying it does not affect the caller. In pass-by-address (passing pointers), the function can mutate the caller’s objects.

Calling Conventions

  1. Write int is_prime(int n).
  2. Write int is_leap_year(int year).
  3. Write int binary_search(const int arr[], int len, int key).
  4. Write void bump(int *p) that increments *p each call.

Composition: Nesting and Chaining

Nesting

One function may call another, but C does not allow nesting definitions.

void line(void) { puts("----"); }

void banner(int n)
{
    for (int i = 0; i < n; ++i)
        line();
}

Chaining

Use the return value of one function as an argument to another.

printf("%d\n", strlen(strcat(buf, "tail")));

Declarations vs. Definitions

  • Declaration – tells the compiler the signature; ends with a semicolon.
  • Definition – provides the body.

Typical multi-file layout:

/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
#endif

/* math_utils.c */
#include "math_utils.h"
int add(int a, int b) { return a + b; }

/* main.c */
#include "math_utils.h"
int main(void) { return add(3, 4); }

Recursion

Recursion is a technique where a function solves a problem by solving smaller instances of itself. Two rules must hold:

  1. There is a base case that stops further recursion.
  2. Each recursive call moves closer to the base case.

Example 1 – printing digits in order

void print_digits(unsigned n)
{
    if (n > 9) print_digits(n / 10);
    printf("%u ", n % 10);
}

Example 2 – string length without local variables

size_t my_strlen(const char *s)
{
    return *s ? 1 + my_strlen(s + 1) : 0;
}

Recursion vs. Iterasion

Recursive solutions are often concise but may sufffer from:

  • Stack overflow for deep recursion.
  • Redundant computations (e.g., naïve Fibonacci).

Iterative rewrite of factorial:

long long fact_iter(int n)
{
    long long acc = 1;
    for (int i = 2; i <= n; ++i) acc *= i;
    return acc;
}

Iterative Fibonacci:

long long fib_iter(int n)
{
    if (n <= 2) return 1;
    long long prev = 1, curr = 1, next;
    for (int i = 3; i <= n; ++i) {
        next = prev + curr;
        prev = curr;
        curr = next;
    }
    return curr;
}

Take-away: choose recursion when clarity outweighs overhead; otherwise prefer iteration.

Further Exploration

  • Towers of Hanoi
  • Frog jumping stairs (count distinct ways to climb n steps)

Tags: C functions Recursion pass-by-value pass-by-address

Posted on Wed, 30 Sep 2026 16:46:38 +0000 by lewis987