intermediate18 min

Header Files and Multi-File Compilation

Splitting a program across .h and .c files, and why #include guards matter.

What you'll learn

  • Explain the difference between a .h header file's declarations and a .c file's definitions
  • Use #include to bring a header's declarations into another file
  • Explain why #include guards prevent a header from being processed twice in one compilation

Explanation

Real C programs are split across multiple files as they grow. The convention is a header file (.h) holding declarations -- what a function's signature is, without its body -- and a source file (.c) holding the actual definitions, the real implementation. #include "mathutils.h" textually pastes the header's contents into whatever file includes it, before compilation proper begins, which is how one file can call a function that's actually implemented in a different .c file.

Because #include is a simple text-paste operation, a header accidentally included twice in the same compilation (directly or indirectly, e.g. through two other headers that both include it) would produce duplicate declarations, which some C constructs don't tolerate. The standard fix is an include guard: wrapping a header's contents in #ifndef HEADER_NAME_H / #define HEADER_NAME_H / #endif. The first time the header is included, HEADER_NAME_H isn't defined yet, so its contents are processed and the macro gets defined; any subsequent #include of the same header in that compilation sees the macro already defined and skips the contents entirely.

To build a multi-file program, you compile each .c file into its own object file, then link them together -- gcc main.c mathutils.c -o main compiles both and links them into one executable in a single command. This separation (declarations in a shared header, one real implementation in one .c file, other files including only the header) is the foundation of how larger C projects stay organized and how libraries expose their public functions without exposing their internal implementation files.

Guided lab

Predict: A program split across a header and two source files

CNot executed
This lab does not run in your browser or on VisaSparkSchools's servers. Read the code, predict what it does, then reveal the real expected output.

This shows three files that together form one program (comments mark each file's boundary). Predict what main.c prints once all three are compiled and linked together.

// file: mathutils.h
#ifndef MATHUTILS_H
#define MATHUTILS_H

int add(int a, int b);

#endif

// file: mathutils.c
#include "mathutils.h"

int add(int a, int b) {
    return a + b;
}

// file: main.c
#include <stdio.h>
#include "mathutils.h"

int main(void) {
    printf("3 + 4 = %d\n", add(3, 4));
    return 0;
}

Stuck? Get a hint.

Common mistakes

  • Putting a function's full implementation inside a header file instead of just its declaration, which risks duplicate-definition errors when the header is included by more than one .c file.
  • Forgetting #include guards, risking the header being processed twice in one compilation if it's included both directly and indirectly.
  • Believing #include is anything more than a literal text-paste of the included file's contents at that point in the source.

Knowledge check

Knowledge check

1. What does a .h header file conventionally contain?
2. What problem do #include guards (#ifndef/#define/#endif) solve?
3. What does the #include directive actually do?

Takeaway

Keep declarations in a shared .h header (guarded with #ifndef/#define/#endif) and the real implementation in one .c file -- #include is just a text-paste, which is exactly why guards matter.

Summary

Multi-file C programs split declarations (headers) from definitions (.c files); #include pastes header text in; include guards stop a header's contents from being processed twice in one compilation.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.