1. Introduction
A header file in C (conventionally with a .h extension) contains declarations — function prototypes, macro definitions, type definitions, and extern variable declarations — that are meant to be shared across multiple source (.c) files. Rather than repeating the same declarations in every file that needs them, you write them once in a header and #include it wherever needed. This is the foundation of modular C programs: a library exposes its public interface through headers while keeping implementation details in separate .c files, and standard headers like stdio.h and stdlib.h expose the declarations for the C standard library functions you use every day.
Cricket analogy: A header file is like a team's official playing conditions document shared with every match official and broadcaster, written once so umpires, scorers, and commentators all reference the same rules without re-explaining them each match.
2. Syntax
/* mathutils.h */
#ifndef MATHUTILS_H
#define MATHUTILS_H
int add(int a, int b);
int subtract(int a, int b);
#endif /* MATHUTILS_H */
/* usage in another file */
#include "mathutils.h"
#include <stdio.h>3. Explanation
A header file typically declares (but does not define/implement) functions using prototypes, so the compiler knows their signatures when they are called from a different .c file than the one that defines them; the actual function bodies live in a corresponding .c source file (e.g., mathutils.c) which is compiled and linked separately. Headers also commonly hold #define constants, typedef declarations, struct/enum definitions, and extern declarations for global variables shared across files. When a header is #include'd, the preprocessor literally pastes its text into the including file at that point — if the same header is included more than once in a single translation unit (directly or indirectly through other headers), its declarations would be duplicated, which the compiler rejects for things like struct or typedef redefinitions. The standard fix is the include guard pattern: wrapping the entire header content in #ifndef UNIQUE_MACRO, #define UNIQUE_MACRO, and a matching #endif. The first time the header is included, UNIQUE_MACRO is undefined, so the body is processed and the macro gets defined; on any subsequent inclusion within the same translation unit, the #ifndef check fails and the body is skipped entirely, preventing duplicate-declaration errors. Many compilers also support #pragma once as a non-standard shorthand for the same effect.
Cricket analogy: Declaring a bowler's pace and style on the team sheet without describing every delivery is like a header's function prototypes; the include guard is like ensuring the team sheet isn't accidentally printed twice in the same program, which would create a duplicate roster entry the scorer would reject.
4. Example
/* shapes.h */
#ifndef SHAPES_H
#define SHAPES_H
typedef struct {
double radius;
} Circle;
double circle_area(Circle c);
#endif /* SHAPES_H */
/* shapes.c */
#include "shapes.h"
#define PI 3.14159
double circle_area(Circle c) {
return PI * c.radius * c.radius;
}
/* main.c */
#include <stdio.h>
#include "shapes.h"
int main(void) {
Circle c = { .radius = 3.0 };
printf("Area = %.2f\n", circle_area(c));
return 0;
}5. Output
Area = 28.27Omitting include guards (#ifndef/#define/#endif) is a common source of multiple-inclusion errors: if a header defines a struct or typedef and gets pulled in twice into the same translation unit (for example, header A includes header B, and the .c file also includes header B directly), the compiler will report redefinition errors. Always wrap every header's content in a unique include guard, or use #pragma once where supported, to make the header safe to include multiple times.
Name your include guard macro after the file path/name to keep it unique across a large project, e.g. MYLIB_SHAPES_H for mylib/shapes.h — generic guard names like HEADER_H risk clashing with an identically named guard in an unrelated header.
6. Key Takeaways
- Header files hold shared declarations (function prototypes, macros, typedefs, struct/enum definitions) — not full function implementations.
- #include textually pastes the header's contents into the including file at that point.
- Include guards (#ifndef/#define/#endif) prevent duplicate declarations when a header is included more than once in a translation unit.
- Function definitions belong in a matching .c file, which is compiled separately and linked with files that include the header.
- Use unique, descriptive guard macro names to avoid clashes across a large codebase.
Practice what you learned
1. What is the primary purpose of a header file in C?
2. What is the correct standard pattern for an include guard?
3. What problem do include guards specifically prevent?
4. Where should a function's actual implementation (body) typically reside in a well-organized C project?
5. What does #pragma once achieve, relative to manual #ifndef/#define/#endif guards?
Was this page helpful?
You May Also Like
Preprocessor Directives in C
Understand C preprocessor directives — #define, #include, #ifdef/#ifndef, and macro pitfalls — with clear examples.
Structures in C
Understand C structures: syntax, dot vs arrow member access, nested structs, and why sizeof() can exceed the sum of member sizes due to padding.
typedef in C
Understand C's typedef keyword to create readable type aliases for structs, pointers, and primitive types.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics