5. Functions, the stack, and header files
5.1 What actually happens on a function call
#include <stdio.h>
int add(int a, int b) {
int result = a + b; // lives in add's stack frame
return result;
}
int main(void) {
int sum = add(3, 4); // a new stack frame is pushed, args copied in (by value),
// frame popped on return, result copied out
printf("%d\n", sum);
return 0;
}
Every local variable and every argument (in C, all arguments are passed by value — there is no pass-by-reference; you simulate it by passing a pointer) lives on the stack, a region that grows and shrinks automatically with function calls. It's fast (just moving a stack pointer) but finite (typically 1–8MB per thread) and disappears the instant the function returns — which is exactly why returning a pointer to a local variable is one of the first bugs every C programmer writes:
// BUG — undefined behavior
int *make_answer(void) {
int answer = 42;
return &answer; // answer's stack frame is gone the moment this returns
}
Clang/GCC with -Wall will warn: warning: address of stack memory associated with local variable 'answer' returned. The fix is either to
return by value, take an output pointer from the caller, or heap-allocate
(Section 10).
5.2 Header files: declarations, not definitions
// point.h
#ifndef POINT_H
#define POINT_H
typedef struct {
double x, y;
} Point;
double point_distance(Point a, Point b);
#endif // POINT_H
// point.c
#include "point.h"
#include <math.h>
double point_distance(Point a, Point b) {
double dx = a.x - b.x, dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
The #ifndef/#define/#endif triple is an include guard — it stops
a header being pasted twice into one translation unit if two .c files
both include something that includes point.h. C23 also supports
#pragma once, which every modern compiler already accepted as an
extension; the include guard remains the portable choice for library code.