What you'll learn
Functions turn a growing sequence of statements into a program with names, boundaries, and reusable ideas. This lesson covers the full function contract, how values cross that boundary, where variables are visible, and how calls occupy the stack.
By the end, you'll be able to:
- Declare, define, call, and test focused functions
- Use prototypes so the compiler can check calls before definitions appear
- Explain C's pass-by-value rule
- Distinguish scope from storage duration
- Write a terminating recursive function and recognize its costs
Function anatomy
A function has a return type, a name, a parameter list, and—when it is a definition—a body. The signature describes how callers supply input and what they receive back.
doublereturn type
averagefunction name
(double total, int count)parameters
{ return total / count; }function body
#include <stdio.h>
double rectangle_area(double width, double height) {
return width * height;
}
void print_area(double area) {
printf("Area: %.2f\n", area);
}
int main(void) {
double area = rectangle_area(4.5, 3.0);
print_area(area);
return 0;
}Tip
void inside an empty parameter list: void show_menu(void). In C, an old-style empty list show_menu() does not provide the same “takes no arguments” prototype.Prototypes
A declaration tells the compiler a function's name and type. A definition additionally supplies the body. A prototype before main lets C verify the call even when the definition appears later.
#include <stdio.h>
double celsius_to_fahrenheit(double celsius); // declaration
int main(void) {
printf("%.1f F\n", celsius_to_fahrenheit(20.0));
return 0;
}
double celsius_to_fahrenheit(double celsius) { // definition
return celsius * 9.0 / 5.0 + 32.0;
}Key idea
Parameters & return values
Parameters are local variables initialized from the caller's arguments. A function may return one value directly. Small predicate functions conventionally return booland often begin with names such as is_, has_, or can_.
#include <stdbool.h>
#include <stdio.h>
bool is_in_range(int value, int minimum, int maximum) {
return value >= minimum && value <= maximum;
}
int clamp(int value, int minimum, int maximum) {
if (value < minimum) return minimum;
if (value > maximum) return maximum;
return value;
}
int main(void) {
printf("%s\n", is_in_range(14, 1, 10) ? "inside" : "outside");
printf("%d\n", clamp(14, 1, 10));
return 0;
}| Form | Meaning |
|---|---|
int next(int value) | Accept an int and return an int |
void print_report(int id) | Accept an int and return no value |
int read_choice(void) | Accept no arguments and return an int |
bool is_valid(int value) | Return a Boolean result |
Pass-by-value
C passes arguments by value: each parameter receives a copy. Reassigning that parameter cannot reassign the caller's object.
#include <stdio.h>
void try_to_change(int value) {
value = 99; // changes only this function's copy
printf("Inside: %d\n", value);
}
int main(void) {
int score = 10;
try_to_change(score);
printf("Caller: %d\n", score);
return 0;
}Note
Scope & storage duration
Scope answers where a name is visible. Storage durationanswers how long its object exists. A local automatic object normally exists for one call; a static object exists for the entire program.
#include <stdio.h>
static int file_total = 0; // file scope, internal linkage
void record_visit(void) {
static int calls = 0; // block scope, static storage duration
int this_visit = 1; // block scope, automatic storage duration
calls++;
file_total += this_visit;
printf("call=%d total=%d\n", calls, file_total);
}
int main(void) {
record_visit();
record_visit();
return 0;
}| Declaration | Name visibility | Object lifetime |
|---|---|---|
| Local automatic | Its enclosing block | From block entry until exit |
| Local static | Its enclosing block | Entire program |
| File-scope static | Remaining translation unit | Entire program |
| File-scope external | Can be declared in other translation units | Entire program |
Minimize shared state
The call stack & recursion
Each active function call has a stack frame containing bookkeeping and its automatic local objects. A recursive function calls itself with a smaller problem and must contain a base case that stops further calls.
#include <stdio.h>
unsigned long long factorial(unsigned int n) {
if (n <= 1U) { // base case
return 1U;
}
return n * factorial(n - 1U); // recursive case
}
int main(void) {
printf("%llu\n", factorial(5));
return 0;
}top of stack
factorial(1)returns 1factorial(2)2 × 1factorial(3)3 × 2main()prints 6Watch out
Clean function design
Functions become easier to reuse and test when they follow a few disciplined rules:
- Give each function one responsibility and a verb-based name
- Keep the interface small and make invalid cases explicit
- Prefer returned results over hidden mutation of global state
- Use early returns for guards when they reduce nesting
- Separate calculation from input/output when practical
#include <stdbool.h>
bool is_valid_percentage(int value) {
return value >= 0 && value <= 100;
}
double percentage(int earned, int possible) {
if (possible <= 0) {
return 0.0;
}
return (double) earned / possible * 100.0;
}Tip
percentage performs a calculation without reading or printing. That makes it easy to call from a terminal program today and a graphical or embedded program later.Recap & quick check
Key takeaways
- A function declaration introduces its type; a definition also provides its body.
- Use (void) to declare a C function that takes no arguments.
- C passes every argument by value, so a parameter begins as a copy.
- Scope controls name visibility, while storage duration controls object lifetime.
- Recursion requires a base case and consumes stack space for every active call.
Quick check
1. What does a function prototype let the compiler check?
2. After a function assigns 99 to an int parameter, what happens to the caller's int?
3. Which object preserves its value between calls?
4. What must every terminating recursive function have?
Phase 1 complete. You can compile C, model data, communicate, choose, repeat, and organize logic into functions. Phase 2 begins with Module 7 — Arrays, where multiple values occupy one contiguous region of memory.