C / PREPROCESSOR
Include guards and pragma once
Make a C header safe to include twice, using an #ifndef guard or #pragma once, and recognise the duplicate-definition errors guards cannot fix.
What you will learn
- Wrap a header in #ifndef/#define/#endif so every inclusion after the first adds nothing
- Derive guard names from the file path so two headers can never share one
- Recognise that guards act per translation unit and never fix link-time duplicates
- Weigh #pragma once against a macro guard: file identity versus a unique macro name
Understanding Include guards and pragma once
Because inclusion is textual, a header that arrives twice in one translation unit has its whole body pasted twice. Some things survive that: `extern int open_port(void);` may be declared any number of times. Definitions do not: a second `struct Point { int x, y; };` in the same scope is an error even though the two bodies are identical, and so is a repeated enumerator or a repeated initialized variable. You rarely control the count, because headers include other headers, so a diamond (main.c includes db.h and log.h, both include config.h) delivers config.h twice without a single duplicated #include line in your own code.
The guard turns the header into something idempotent. On the first arrival the macro is undefined, so the body is kept and the macro is defined; on every later arrival in the same translation unit the #ifndef is false and the file contributes zero tokens, exactly as if it were empty. The memory here is the preprocessor's macro table, and that table is created fresh for each .c file, which is why a guard says nothing about what other translation units contain. The key of the latch is a name, so that name has to be unique across your project and every library you include, or one header silently blanks another.
`#pragma once` does the same job keyed on file identity rather than on a name: the compiler remembers which files it has already opened, usually by device and inode number or by resolved path, and skips reopening them. It is not in the C standard but GCC, Clang and MSVC all implement it. The trade-off is which failure mode you accept: file identity can be ambiguous, so two copies of a header, a hard link, or a header reachable through two different -I paths can still be read twice, while a macro guard cannot be fooled by that and instead breaks on name collisions. Speed is no longer part of the argument, since GCC and Clang detect the guard idiom and avoid re-reading a guarded file at all.
<stdio.h>
/* This is what the compiler actually sees when a guarded header
reaches one translation unit twice: the text arrives twice. */
/* ---- first copy of point.h ---- */
POINT_H
POINT_H
struct Point { int x, y; };
static const char *point_source = "first copy";
/* ---- second copy of the same point.h ---- */
POINT_H
POINT_H
struct Point { int x, y; };
static const char *point_source = "second copy";
int main(void)
{
struct Point p = { 3, 4 };
printf("point_source = %s\n", point_source);
printf("p = (%d, %d)\n", p.x, p.y);
POINT_H
printf("POINT_H is still defined, so copy 2 expanded to nothing\n");
return 0;
}
A header is only reusable if a second inclusion in the same translation unit produces no text at all, and that is the single thing both #ifndef guards and #pragma once buy you.
Worked examples
The diamond: a header included through another header
Shows the nested second arrival of config.h being skipped, which is the case you cannot avoid by tidying your own #include lines.
<stdio.h>
/* --- config.h, reaching this file for the first time --- */
CONFIG_H
CONFIG_H
enum { MAX_USERS = 4 };
/* --- db.h, which itself includes config.h --- */
DB_H
DB_H
CONFIG_H
CONFIG_H
enum { MAX_USERS = 4 };
static int slots = MAX_USERS;
int main(void)
{
printf("MAX_USERS = %d\n", MAX_USERS);
printf("slots = %d\n", slots);
return 0;
}
Example explained
Line 1The outer CONFIG_H block is kept, so MAX_USERS becomes an enumerator and CONFIG_H is defined.
Line 2The copy nested inside db.h tests the same CONFIG_H, finds it defined, and contributes nothing.
Line 3Had it not been skipped, `enum { MAX_USERS = 4 };` would redeclare the enumerator MAX_USERS, which is an error even with an identical value.
Line 4`slots` still compiles because the guard only suppresses the repeated text, not the definitions the first copy already made.
Two headers sharing a guard macro
Shows what a copy-pasted guard name does: the second header vanishes without any diagnostic at the point of inclusion.
<stdio.h>
/* --- colour.h --- */
UTIL_H
UTIL_H
static const char *colour = "red";
COLOUR_BODY_RAN
/* --- shape.h, copied from colour.h with the guard name left alone --- */
UTIL_H
UTIL_H
static const char *shape = "circle";
SHAPE_BODY_RAN
int main(void)
{
COLOUR_BODY_RAN
printf("colour.h body ran, colour = %s\n", colour);
SHAPE_BODY_RAN
printf("shape.h body ran, shape = %s\n", shape);
printf("shape.h body never ran: UTIL_H was already defined\n");
return 0;
}
Example explained
Line 1colour.h defines UTIL_H first, so its body is kept and COLOUR_BODY_RAN exists.
Line 2shape.h tests that same UTIL_H, sees it defined, and expands to nothing; the preprocessor has no way to know these are different files.
Line 3The #else branch proves neither `shape` nor SHAPE_BODY_RAN was ever created.
Line 4Referring to `shape` outside the #ifdef would produce an 'undeclared identifier' error in main, nowhere near the misnamed guard that caused it.
Important notes
`#pragma once` is not part of the C standard. GCC, Clang and MSVC support it, but since it keys on file identity, a duplicated header file, a hard link, or a path reached through two different -I directories can still be processed twice.
Guard names such as `_POINT_H_` or `__POINT_H__` fall in the identifier space reserved for the implementation: a leading underscore before a capital letter, and any double underscore anywhere, may clash with the compiler's own macros. Write POINT_H.
Common mistakes
Closing the guard early, often with `#endif` right after the header's own #include lines: everything below it stays unguarded, so the second inclusion reports redefinition of exactly those types while the guard looks correct at a glance.
Copying a header and forgetting to rename its guard macro, so the new header expands to nothing and the compiler complains about undeclared identifiers at every use site instead of pointing at the header.
Expecting a guard to prevent `multiple definition of 'log_level'` at link time; a header containing `int log_level = 3;` still creates one definition in every .c that includes it, so the header needs `extern int log_level;` with the definition in exactly one .c.
Try it yourself
Change, predict, then run
In one file, paste the same `#ifndef DEMO_H` block twice, where the body defines `struct Demo { int n; };` plus `static int copy_id = 1;` in the first copy and `copy_id = 2` in the second, and print copy_id. Then delete the `#ifndef`/`#define`/`#endif` lines from the second copy and read the exact redefinition errors the compiler reports.
Open the C workspaceCheck your understanding
log.h is guarded with #ifndef LOG_H and contains `int log_level = 3;`. Both main.c and net.c include it, and the two object files are linked together. What happens?
- The link fails: each translation unit gets its own definition of log_level, and the guard cannot reach across separately compiled files.
- It builds and links fine, because the include guard makes sure log_level is created only once in the whole program.
- net.c fails to compile, because LOG_H is already defined from main.c.
- It builds and links, but the two files end up using different variables named log_level.
Show answer
The guard's state lives in the preprocessor's macro table, which starts empty for every translation unit, so log.h is expanded once in main.c and once in net.c and each produces an external definition of log_level; the linker rejects the duplicate. Option 2 is tempting because guards genuinely do stop repetition inside one translation unit, but nothing about them crosses the compile step; the fix is `extern int log_level;` in the header with `int log_level = 3;` in exactly one .c file.