Modular Programming
As a C program grows beyond a few hundred lines, cramming everything into a single .c file becomes unmanageable. Modular programming splits a program into multiple modules — pairs of files, each responsible for one coherent piece of functionality — that are compiled separately and linked together into a single executable.
The .c / .h Pair
A header file (
.h) declares the module's public interface: function prototypes, type definitions, and constants that other modules are allowed to use.A source file (
.c) contains the actual implementation — function bodies, and any private (static) helpers that only this module needs.Other modules
#includethe header to gain access to the interface, without needing to see (or recompile) the implementation.
Interface vs Implementation
This split mirrors a general engineering principle: separate what something does from how it does it. A header describes a contract — the function signatures and types callers can rely on — while the source file is free to change its internal implementation at any time, as long as it still fulfills that contract.
/* stack.h -- the public interface (the "what") */ #ifndef STACK_H #define STACK_H typedef struct Stack Stack; Stack *stackCreate(int capacity); void stackDestroy(Stack *s); int stackPush(Stack *s, int value); int stackPop(Stack *s, int *outValue); #endif /* STACK_H */
/* stack.c -- the private implementation (the "how") */
#include <stdlib.h>
#include "stack.h"
struct Stack {
int *items;
int capacity;
int top;
};
Stack *stackCreate(int capacity) {
Stack *s = malloc(sizeof(Stack));
if (s == NULL) {
return NULL;
}
s->items = malloc(sizeof(int) * capacity);
s->capacity = capacity;
s->top = -1;
return s;
}
void stackDestroy(Stack *s) {
if (s != NULL) {
free(s->items);
free(s);
}
}
int stackPush(Stack *s, int value) {
if (s->top + 1 >= s->capacity) {
return 0;
}
s->items[++(s->top)] = value;
return 1;
}
int stackPop(Stack *s, int *outValue) {
if (s->top < 0) {
return 0;
}
*outValue = s->items[(s->top)--];
return 1;
}Using the Module from main.c
/* main.c -- a consumer of the stack module */
#include <stdio.h>
#include "stack.h"
int main(void) {
Stack *s = stackCreate(10);
stackPush(s, 1);
stackPush(s, 2);
stackPush(s, 3);
int value;
while (stackPop(s, &value)) {
printf("Popped: %d\n", value);
}
stackDestroy(s);
return 0;
}Why Bother?
Benefit | Explanation |
|---|---|
Compile time | Only the module you changed needs to be recompiled, not the whole program |
Reusability | A well-designed module (like stack.c) can be dropped into other projects unchanged |
Team collaboration | Different people can work on different modules with minimal file conflicts |
Testability | A module with a clear interface can be tested in isolation, independent of the rest of the program |
Readability | Smaller, focused files are far easier to understand than one giant file |
A Typical Project Layout
project/ main.c # program entry point, ties modules together stack.h # public interface for the stack module stack.c # stack implementation queue.h # public interface for the queue module queue.c # queue implementation utils.h # small shared helper functions utils.c