Tutorials Logic, IN info@tutorialslogic.com

C Functions Declaration, Recursion, Pointers

Function Contracts

A C function has a return type, name, parameter list, and body. A declaration lets the compiler check calls before it sees the definition. Strong function design goes beyond syntax: the caller and function need a clear agreement about valid inputs, returned status, modified memory, and ownership.

After this lesson, you can declare and call a function with type checking, explain C’s pass-by-value behavior, use pointers for output or mutation, keep array bounds explicit, and separate a public declaration from a private implementation.

Function Purpose

A function is a reusable block of code that performs a specific task. Functions help break a large program into smaller, manageable pieces. Every C program has at least one function - main().

Function Declaration, Definition and Call

  • Declaration (Prototype) - tells the compiler about the function's name, return type, and parameters. Placed before main().
  • Definition - the actual body of the function with its implementation.
  • Call - invoking the function to execute it.

Function Declaration, Definition and Call - C Example

Function Declaration, Definition and Call - C Example
// Declaration (prototype)
int add(int a, int b);

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

// Call
int result = add(5, 3);  // result = 8

Variable Scope

  • Local variables - declared inside a function, exist only within that function
  • Global variables - declared outside all functions, accessible everywhere
  • Static local variables - retain their value between function calls

Function Declaration, Definition and Call - C Example 2

Function Declaration, Definition and Call - C Example 2
#include <stdio.h>

// Function prototypes (declarations)
int add(int a, int b);
float average(int a, int b, int c);
void greet(char name[]);
void countCalls();

int main() {
    printf("Sum: %d\n", add(10, 5));
    printf("Average: %.2f\n", average(10, 20, 30));
    greet("Alice");

    // Static variable demo
    countCalls();
    countCalls();
    countCalls();

    return 0;
}

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

float average(int a, int b, int c) {
    return (a + b + c) / 3.0f;
}

void greet(char name[]) {
    printf("Hello, %s!\n", name);
    // no return statement needed for void
}

void countCalls() {
    static int count = 0;  // retains value between calls
    count++;
    printf("Function called %d time(s)\n", count);
}

/*
Output:
Sum: 15
Average: 20.00
Hello, Alice!
Function called 1 time(s)
Function called 2 time(s)
Function called 3 time(s)
*/

Recursion - Factorial and Fibonacci

Recursion - Factorial and Fibonacci
#include <stdio.h>

// Recursive factorial: n! = n * (n-1)!
long long factorial(int n) {
    if (n == 0 || n == 1) return 1;  // base case
    return n * factorial(n - 1);     // recursive call
}

// Recursive Fibonacci: fib(n) = fib(n-1) + fib(n-2)
int fibonacci(int n) {
    if (n <= 1) return n;            // base cases: fib(0)=0, fib(1)=1
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    // Factorial
    for (int i = 0; i <= 10; i++) {
        printf("%d! = %lld\n", i, factorial(i));
    }

    // Fibonacci series
    printf("\nFibonacci (first 10): ");
    for (int i = 0; i < 10; i++) {
        printf("%d ", fibonacci(i));
    }
    printf("\n");

    return 0;
}

/*
0! = 1
1! = 1
...
10! = 3628800

Fibonacci (first 10): 0 1 1 2 3 5 8 13 21 34
*/

Call by Value vs Call by Reference

Call by Value vs Call by Reference
#include <stdio.h>

// Call by value - a copy is passed; original is NOT modified
void doubleByValue(int x) {
    x = x * 2;
    printf("Inside doubleByValue: %d\n", x);
}

// Call by reference - pointer is passed; original IS modified
void doubleByRef(int *x) {
    *x = *x * 2;
    printf("Inside doubleByRef: %d\n", *x);
}

// Swap using pointers
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int num = 10;

    doubleByValue(num);
    printf("After doubleByValue: %d\n\n", num);  // still 10

    doubleByRef(&num);
    printf("After doubleByRef: %d\n\n", num);    // now 20

    int a = 5, b = 8;
    printf("Before swap: a=%d, b=%d\n", a, b);
    swap(&a, &b);
    printf("After swap:  a=%d, b=%d\n", a, b);

    return 0;
}

/*
Inside doubleByValue: 20
After doubleByValue: 10

Inside doubleByRef: 20
After doubleByRef: 20

Before swap: a=5, b=8
After swap:  a=8, b=5
*/

Declarations and Definitions

Place declarations shared by several source files in a guarded header and put definitions in one .c file. Use parameter names in declarations when they improve meaning. Write f(void), not f(), when a function intentionally accepts no arguments: in C, an empty parameter list declaration does not provide the same prototype information.

The declaration and definition must agree exactly on return and parameter types. Include the header in the implementation file so the compiler checks that agreement. Do not rely on implicit function declarations; modern C requires a visible declaration before a call.

Values and Pointers

C passes every argument by value. Passing an int copies the integer; assigning to the parameter does not change the caller’s object. Passing a pointer also copies a value, but that pointer value identifies the caller’s memory, so dereferencing it can modify the pointed object. Check whether NULL is allowed and document that part of the contract.

An array parameter is adjusted to a pointer parameter and does not carry its element count. Pass the length separately and keep the units clear. sizeof on an array parameter returns the size of a pointer, not the caller’s complete array. Use const for pointed data the function reads but must not modify.

Results and Errors

A function can return a computed value directly when every value in the type is a valid result. When failure must be represented separately, return a status code and write the result through an output pointer, or return a pointer with NULL as a documented failure. Do not return the address of an automatic local variable because its lifetime ends when the function returns.

Keep one function focused enough that its name and contract remain accurate. A short helper is useful when it isolates a decision, invariant, or resource operation; splitting every expression into a function only makes control flow harder to follow.

Before you move on

Contract Review

4 checks
  • Provide a visible prototype before every call.
  • Use (void) for a function with no parameters.
  • Pass array lengths and document pointer nullability.
  • Define result, error, and ownership behavior.

Function Defects

  • Expecting a scalar parameter assignment to change the caller.

    Return the new value or accept a pointer to writable storage.
  • Using sizeof on an array parameter to find its length.

    Pass the element count explicitly.
  • Returning a pointer to a local array.

    Use caller-owned storage, static lifetime with care, or documented dynamic allocation.

Try this next

Write a Clear API

0 of 2 completed

Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.