Last 30 Days
No notifications
A function is a named block of code you can call from anywhere. Functions let you split a big problem into small, testable pieces.
int add(int a, int b) {
return a + b;
}int main(void) {
int result = add(3, 5); // call the function
printf("%d\n", result); // 8
}
return_type name(parameter_type p1, parameter_type p2) {
/* body */
return some_value;
}| Part | Meaning |
return_type | What the function gives back (int, double, void, …) |
name | What you call it by |
parameters | Inputs the function receives |
return | Sends a value back to the caller |
void | "no return value" or "no parameters" |
/* Returns a value */
int square(int x) { return x * x; }/* Returns nothing */
void greet(const char *name) {
printf("Hello, %s!\n", name);
}
/* Takes nothing, returns nothing */
void banner(void) {
printf("======\n");
}
C reads files top-to-bottom. If main calls add before add is defined, you must declare it at the top:
int add(int a, int b); // prototype — just the signatureint main(void) {
return add(2, 3);
}
int add(int a, int b) { return a + b; } // definition later
C always copies arguments. Modifying a parameter inside a function does not affect the caller's variable.
void inc(int x) { x++; }
int n = 5;
inc(n);
printf("%d\n", n); // still 5To modify the caller's variable, pass a pointer (covered in the Pointers topic).
Functions are the unit of structure in C. A C program is, fundamentally, a collection of functions — main being just one of them.
Three reasons:
1. Reuse — write logic once, call it many times. 2. Abstraction — give a name to "what" instead of repeating "how". 3. Testability — small functions are easy to verify.
A program of 100 lines of main is hard to debug. A program of one 5-line main plus ten 10-line helpers is easy.
int add(int, int); /* declaration (prototype) — promises it exists */
int add(int a, int b) { ... } /* definition — actual body */You can declare many times, but define exactly once. Prototypes go in header files (.h); definitions go in source files (.c).
> Pre-C99, omitting the prototype made the compiler assume int foo(). This is gone in C99+ — you must declare before use.
void in int main(void)?Without void, the empty parentheses int main() historically meant "any number of arguments — I haven't told you yet". The modern, correct form is:
int main(void) { ... }void explicitly says "no parameters".
C has no pass-by-reference. Every parameter is a copy:
void swap_wrong(int a, int b) {
int t = a; a = b; b = t; // swaps the copies — caller unchanged!
}int x = 1, y = 2;
swap_wrong(x, y);
printf("%d %d", x, y); // 1 2 — nothing changed
To actually modify the caller's variables, pass addresses (pointers):
void swap_right(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}int x = 1, y = 2;
swap_right(&x, &y);
printf("%d %d", x, y); // 2 1
The & takes the address; * dereferences it. We'll go deep on pointers next phase — but you've now seen why they exist.
When you pass an array to a function, only the address of the first element is copied — not the whole array. That's why:
1. Modifying the array inside the function does affect the caller's array.
2. sizeof(arr) inside the function gives pointer size, not array size.
void zero(int a[], int n) { /* a[] is really int * */
for (int i = 0; i < n; i++) a[i] = 0;
}This is why every array-taking function in C also takes the length as a separate parameter.
int absolute(int x) {
if (x < 0) return -x;
return x;
}return immediately exits the function. You can have multiple returns — early-exits often make code clearer than deep nesting.
void functions can use return; (no value) to exit early.
Variables declared inside a function are local — they exist only while the function runs and are invisible elsewhere.
int counter(void) {
int c = 0; /* fresh each call — always starts at 0 */
return ++c;
}To preserve a value between calls, use static:
int counter(void) {
static int c = 0; /* initialised ONCE; survives between calls */
return ++c;
}
counter(); // 1
counter(); // 2
counter(); // 3static locals live for the entire program.
Every function call gets a stack frame: a chunk of memory holding parameters, locals, and the return address. When the function returns, its frame is popped.
main's frame
add's frame
← top — gets popped when add returnsThat's why returning a pointer to a local variable is a bug — the memory is reclaimed:
int *bad(void) {
int x = 42;
return &x; /* ❌ x dies when bad returns; pointer is dangling */
}A function calling itself is recursion:
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}Each call adds a stack frame. Beautiful for tree/divide-and-conquer problems but heavy on the stack — too deep and you get a stack overflow. Recursion gets its own dedicated topic in Phase 4.
/* mathx.h — declarations */
#ifndef MATHX_H
#define MATHX_H
int add(int, int);
int square(int);
#endif/* mathx.c — definitions */
#include "mathx.h"
int add(int a, int b) { return a + b; }
int square(int x) { return x * x; }
/* main.c — uses them */
#include "mathx.h"
int main(void) { return add(square(3), 4); }
The #ifndef … #define … #endif "include guard" prevents the same header from being parsed twice.
| Need | Pattern |
| Return a value | int f(...) { return v; } |
| Don't return | void f(...) { } |
| No parameters | int f(void) |
| Modify caller's var | Pass &var, take int * |
| Operate on array | Take pointer + length |
| Persistent local | static int n; |
Functions are how you go from "writing code" to "designing programs."