Skip to main content

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:

  1. The compiler assumes a K&R-style implicit int signature and silently misreads your arguments.
  2. The linker complains about undefined reference.
  3. 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

  1. For each function you want to share, put one declaration in a header.
  2. Put each definition in exactly one .c file.
  3. The defining .c file must #include its own header so the prototype is checked against the definition.
  4. Use static on helper functions that should not be visible outside one .c file (see the next concept on linkage).
  5. Use void for no-parameter functions: int f(void); not int f();. The second is an old K&R signal for "unknown arguments."

Check Yourself

  1. What goes in a header and what does not?
  2. What happens at compile time if you call a function that has no visible prototype?
  3. 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:

  1. Move two helper functions into util.c with matching prototypes in util.h.
  2. #include "util.h" from both .c files.
  3. Build with gcc -Wall -Wextra -std=c11 -o app main.c util.c.
  4. Remove one prototype from util.h and describe the compiler error.
  5. Put a full function body (not static inline) in the header and describe the linker error when both .c files include it.

Read This Only If Stuck