Functions, Declarations vs Definitions, Header Files
What This Concept Is
C separates declaration (what exists and how to call it) from definition (the code or storage that realizes it).
- A function declaration (a "prototype") names the function, its return type, and its parameter types. It tells the compiler how to type-check calls. It has no body.
- A function definition has a body and produces code the linker can place in the final executable. There must be exactly one definition per function across the whole program (C's "one-definition rule" for external linkage).
Header files exist to share declarations. They do not contain function bodies. The .c file that defines the function #includes the header so the compiler checks the definition against the declared prototype.
Why It Matters Here
Once you write a second .c file, every cross-file call is mediated by a declaration. Get this wrong and one of three things happens:
- The compiler assumes a K&R-style implicit
intsignature and silently misreads your arguments. - The linker complains about
undefined reference. - The program "works" but passes or returns the wrong types, which is undefined behavior.
Building the habit of "declaration in header, definition in one .c file, include header everywhere it is used" is the backbone of every real C project.
Concrete Example
A clean two-file program.
math_util.h:
#ifndef MATH_UTIL_H
#define MATH_UTIL_H
int add(int a, int b); /* declaration / prototype */
int max(int a, int b); /* declaration / prototype */
#endif
math_util.c:
#include "math_util.h"
int add(int a, int b) { return a + b; } /* definition */
int max(int a, int b) { return a > b ? a : b; } /* definition */
main.c:
#include <stdio.h>
#include "math_util.h"
int main(void) {
printf("%d %d\n", add(2, 3), max(2, 3));
return 0;
}
Build with gcc -Wall -Wextra -std=c11 -o app main.c math_util.c. The compiler sees the prototype in both .c files via the header; the linker sees one definition of each function.
Common Confusion / Misconception
"The prototype is just documentation." No. Without it, a call to add(2, 3) in a file that did not see the declaration would default to implicit int return and no argument checking in C89, which is undefined in C99+. Modern compilers warn on missing prototypes with -Wall; promote that to -Werror once you are comfortable.
"I can put a function body in a header." Only if it is marked static inline. A plain function definition in a header that is included by two .c files yields multiple definition at link time.
How To Use It
- For each function you want to share, put one declaration in a header.
- Put each definition in exactly one
.cfile. - The defining
.cfile must#includeits own header so the prototype is checked against the definition. - Use
staticon helper functions that should not be visible outside one.cfile (see the next concept on linkage). - Use
voidfor no-parameter functions:int f(void);notint f();. The second is an old K&R signal for "unknown arguments."
Check Yourself
- What goes in a header and what does not?
- What happens at compile time if you call a function that has no visible prototype?
- Why must each function have exactly one definition in a linked program?
Mini Drill or Application
Take a single-file program you have written, split it into a util.c / util.h / main.c trio:
- Move two helper functions into
util.cwith matching prototypes inutil.h. #include "util.h"from both.cfiles.- Build with
gcc -Wall -Wextra -std=c11 -o app main.c util.c. - Remove one prototype from
util.hand describe the compiler error. - Put a full function body (not
static inline) in the header and describe the linker error when both.cfiles include it.