Real C projects split code across multiple .c files, sharing declarations through .h header files rather than putting everything in one giant file.
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b); // declaration only — no body
int multiply(int a, int b);
#endif
// math_utils.c
#include "math_utils.h"
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
// main.c
#include <stdio.h>
#include "math_utils.h" // quotes for local headers, <> for system headers
int main(void) {
printf("%d\n", add(2, 3));
return 0;
}
The header declares what functions exist (their prototypes); the corresponding .c file defines how they work. Any other file that wants to call add or multiply just #includes the header, without needing to see or recompile the implementation. Angle brackets (<stdio.h>) tell the compiler to search standard system directories; quotes ("math_utils.h") tell it to look in the local project directory first.
Compiling a multi-file project means compiling each .c file and linking the resulting object files together:
gcc -c math_utils.c -o math_utils.o
gcc -c main.c -o main.o
gcc math_utils.o main.o -o app
For anything beyond a couple of files, a Makefile automates this so you don't retype these commands by hand, and only rebuilds files that actually changed since the last build — most real C projects use make or a more modern build system (CMake, Meson) for exactly this reason.