Learn C Language - Functions & Modular Programming
Episode 5 of 24

Learn C Language - Functions & Modular Programming

This episode builds functions as modular units: definitions and prototypes, variable scope, passing parameters by value, and return values. You will also assemble multi-file programs with header files, and get to know recursion, inline functions, and function pointers.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

A healthy program isn't written as one giant block; it's broken into small functions, each with a single responsibility. Episode 5 covers how to build and assemble those functions into a modular program.

Functions in C work on the passing by value concept: when a variable is sent to a function, what gets sent is a copy of its value, not the original variable. This concept often confuses beginners, but it's actually the key to safe design. To modify original data, you need pointers — a topic we'll dissect in episode 7.

You will also learn to separate code into header files and source files, build multi-file projects, and get to know recursion, inline functions, and function pointers that open the door to callbacks and generic programming.

Function Definitions, Prototypes, and Scope

Function Anatomy

A function consists of a return type, a name, a parameter list, and a function body. Functions that return no value are declared with the void type:

Definition and prototype
cat > fungsi.c <<'EOF'
#include <stdio.h>
 
int tambah(int a, int b);
 
int main(void) {
    int hasil = tambah(3, 4);
    printf("Hasil: %d\n", hasil);
    return 0;
}
 
int tambah(int a, int b) {
    return a + b;
}
EOF
gcc -Wall -Wextra fungsi.c -o fungsi && ./fungsi

The line int tambah(int a, int b); is a prototype: a declaration that tells the compiler about the function's signature before its definition appears. With a prototype, main can call tambah even though its definition comes later.

Variable Scope

Every variable has a scope that determines where it can be accessed. Local variables live only within the block where they're declared, including function parameters. Global variables are declared outside all functions and can be accessed anywhere — but use them sparingly because they make it hard to track who changes the value.

Passing Parameters by Value

Values Are Copied, Not Borrowed

When a function is called, the argument values are copied into the parameters. Changes inside the function don't affect the caller's original variables:

Passing by value
cat > byvalue.c <<'EOF'
#include <stdio.h>
 
void ubah(int x) {
    x = 99;
}
 
int main(void) {
    int nilai = 5;
    ubah(nilai);
    printf("nilai tetap %d\n", nilai);
    return 0;
}
EOF
gcc -Wall -Wextra byvalue.c -o byvalue && ./byvalue

Even though ubah sets x to 99, the variable nilai in main remains 5. This is passing by value behavior: only a copy is sent. To change the original nilai, the function must receive its address through a pointer, as we'll cover in episode 7.

Return Values

A function returns its result via the return statement. This value can be used directly or stored in a variable. A simple rule: if a function produces a value used elsewhere, make it return that value rather than writing to a global variable.

Header Files and Modularization

Separating Declaration and Implementation

For projects that grow large, separate the interface from the implementation. Header .h files contain the declarations other files are allowed to see, while source .c files contain the definitions. The standard layout of a module:

Two-file module
cat > math_utils.h <<'EOF'
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
 
int kuadrat(int x);
 
#endif
EOF
cat > math_utils.c <<'EOF'
#include "math_utils.h"
 
int kuadrat(int x) {
    return x * x;
}
EOF
cat > main.c <<'EOF'
#include <stdio.h>
#include "math_utils.h"
 
int main(void) {
    printf("Kuadrat 6 = %d\n", kuadrat(6));
    return 0;
}
EOF
gcc -Wall -Wextra math_utils.c main.c -o app && ./app

The command gcc math_utils.c main.c -o app compiles both source files at once and links them. The MATH_UTILS_H symbol in #ifndef serves as an include guard that prevents the header from being processed twice — a topic we'll cover in detail in episode 10.

Translation Units

Each preprocessed .c file is called a translation unit. Interfaces between translation units are bridged by headers. Good discipline: every .c file includes its matching header, and every function used from outside must be declared in a header.

Recursion, Inline, and Function Pointers

Recursion

Recursive functions call themselves. Each call needs a base case that stops the recursion; otherwise a stack overflow occurs. The classic factorial example:

Recursive function
#include <stdio.h>
 
int faktorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * faktorial(n - 1);
}
 
int main(void) {
    printf("%d\n", faktorial(5));
    return 0;
}

The function faktorial(5) calls itself until it reaches n = 1 as the base case. Recursion is elegant for structures like trees, but for simple iteration a for loop is more efficient.

Inline Functions and Function Pointers

The inline keyword suggests the compiler insert the function body directly at the call site, avoiding call overhead. It's only a suggestion; the compiler makes the final decision.

A function pointer stores the address of a function, so a function can be chosen and called dynamically:

Function pointer as callback
#include <stdio.h>
 
int tambah(int a, int b) { return a + b; }
int kali(int a, int b) { return a * b; }
 
int main(void) {
    int (*operasi)(int, int) = kali;
    printf("%d\n", operasi(4, 5));
    return 0;
}

The declaration int (*operasi)(int, int) creates a variable that points to a function taking two ints and returning an int. Function pointers are the foundation of callbacks, dispatch tables, and generic programming.

Tip

A function should do one thing and one thing only. If a function exceeds your editor screen, consider splitting it up. This makes testing and code review much easier.

Closing

Key takeaways:

  • Function prototypes allow calling before definition.
  • Local variable scope is limited to the block where it's declared.
  • C uses passing by value; changes inside a function don't affect the original arguments.
  • Header files hold declarations, source files hold definitions.
  • Recursion needs a base case to prevent stack overflow.
  • Function pointers enable callbacks and dynamic dispatch.

In the next episode 6 we will discuss complex data types and arrays — one- and multi-dimensional arrays, strings as character arrays, struct, union, and enum as composite types, and typedef, aliases, and simple data structures.

Learn C Language - Functions & Modular Programming | Learn C Language