5.1.27 · D5C Programming
Question bank — Preprocessor directives — #define, #ifdef, #ifndef, #include guards
True or false — justify
Every #define reserves memory for a value, like a variable does.
False — a macro is pure text substitution; it has no memory address, no type, and vanishes before the compiler runs. Only real objects like
const int reserve memory.#define PI 3.14159 and const double PI = 3.14159; are interchangeable in every way.
False — the macro is untyped text with no scope and is invisible to the debugger; the
const is a typed object with an address and normal scoping. They only look alike.A macro respects C's block scope, so #defineing inside a function limits it to that function.
False — the preprocessor ignores
{} and functions entirely; a macro is live from its #define line to the end of the file (or until #undef), regardless of braces.#ifdef DEBUG checks whether DEBUG has a non-zero value.
False —
#ifdef only asks "does this name exist as a macro?" Even #define DEBUG 0 still makes #ifdef DEBUG true, because the name is defined.Wrapping a macro body in parentheses is just a style preference.
False — it changes results. Without them, blind pasting lets surrounding operators mis-group the text, so
SQUARE(a+b) becomes a+b*a+b instead of the intended product.#pragma once and the classic #ifndef guard achieve the same goal.
True — both ensure a header's body is included at most once per translation unit;
#ifndef is standard and portable, #pragma once is a widely-supported compiler extension.Two different headers can safely reuse the guard tag HEADER_H.
False — whichever header is included first defines
HEADER_H, so the second header sees its guard as already-defined and gets entirely deleted. Tags must be unique.The preprocessor removes an #ifdef block by generating machine code that skips it at runtime.
False — there is no runtime cost at all; the preprocessor deletes the text before compilation, so the disabled lines never become code in the first place.
#include "file.h" copies a reference to the header into your file.
False — it copies the header's entire text in place, as if you had pasted it by hand. That literal duplication is exactly why include guards are needed.
Spot the error
#define MAX 100; used as int arr[MAX]; — what breaks?
The trailing
; is part of the substituted text, so it expands to int arr[100;];, a syntax error. Directives are not C statements — never end them with a semicolon.#define SQUARE(x) x * x used as SQUARE(3 + 1) — why not 16?
It expands to
3 + 1 * 3 + 1 = 7 because * binds tighter than +. Fix: #define SQUARE(x) ((x) * (x)).#define HALF(x) (x) / 2 used as 1 / HALF(4) — why not 1/2?
It expands to
1 / (4) / 2 = (1/4)/2, not 1 / 2. The body needs outer parentheses too: #define HALF(x) ((x) / 2).#ifdef WINDOWS
use_win();
#else
use_posix();What is missing?
The
#endif. Every #ifdef/#ifndef/#if block must be closed, or the preprocessor keeps swallowing lines to the end of the file and errors out.#ifndef CONFIG_H
struct Cfg { int n; };
#endifWhy does this fail to guard properly?
It never
#defines CONFIG_H, so #ifndef is true on every inclusion and the struct still gets pasted twice. The middle #define CONFIG_H line is mandatory.#define NAME = value (with an =) — what goes wrong?
The
= is substituted as text, so int x = NAME; becomes int x = = value;. #define uses a space, not an equals sign: #define NAME value.A macro #define ADD(a,b) a+b used as ADD(1,2) * 3 gives 7, not 9 — why?
It expands to
1+2 * 3 = 1 + 6 = 7. Wrap the whole body: #define ADD(a,b) ((a)+(b)) so it becomes ((1)+(2)) * 3 = 9.Why questions
Why does the preprocessor need parentheses when a real function never does?
A function evaluates its arguments to values first, so precedence is already settled. A macro pastes raw text, so surrounding operators can reach inside and regroup it — parentheses fence off that text.
Why is #ifndef (not #ifdef) the guard for headers and default values?
You want to act only when something is absent — define the header body the first time (before its tag exists), or supply a default only when no external value was given. That "if not already there" logic is exactly
#ifndef.Why can commenting out one #define DEBUG line shrink your binary?
All the debug code sits inside
#ifdef DEBUG; without the define, the preprocessor deletes that text, so those printfs never reach the compiler and never appear in the executable.Why does a build flag like -DBUFFER_SIZE=1024 win over an in-file default?
The
-D flag defines the macro before your file is processed, so by the time the file's #ifndef BUFFER_SIZE runs, the name already exists and the default block is skipped. See Build flags -D and the command line.Why is a duplicated struct definition an error but a duplicated function declaration often is not?
Redefining a
struct gives the compiler two conflicting descriptions of the same type; redeclaring a function just repeats the same signature harmlessly. Headers full of struct definitions are the reason guards exist — see Header files and translation units.Why does the compiler never see the word PI from #define PI 3.14159?
The preprocessor runs first and replaces every
PI token with 3.14159; the translation unit handed to the compiler has no # lines and no macro names left. This ordering is the whole pipeline.Why prefer const/enum over #define for typed constants, but keep #define for guards?
Typed constants give you type-checking, scope, and debugger visibility that text macros lack; but guards and configuration toggles are about controlling text inclusion, which only the preprocessor can do.
Edge cases
If a header has no include guard but is included only once, does it matter?
Not for that build — one inclusion pastes the body once with no conflict. But the moment another file includes it transitively, you get double-pasting; guards are cheap insurance you add regardless.
#define EMPTY (a name with no replacement text) — is that legal, and what does #ifdef EMPTY do?
Legal — the name is defined to empty text.
#ifdef EMPTY is true (it exists), and using EMPTY in code substitutes nothing, effectively deleting the token.What happens if you #define a macro that's already defined with a different body?
The compiler warns/errors about redefinition (unless the bodies are identical). To reassign intentionally you must
#undef it first, then #define again.Does #ifndef MAX / #define MAX 100 / #endif protect against MAX being redefined later in the same file?
No — it only prevents defining
MAX if it's unset at that point. Anything after the #endif is free to #undef and redefine it; the guard is a one-time gate, not a lock.Can a macro expand to reference itself, e.g. #define N N + 1?
It expands only once — the preprocessor blocks a macro from recursively expanding its own name, so
N becomes N + 1 and the inner N is left as-is (a real symbol or an error), preventing infinite substitution.If #ifdef DEBUG wraps code with a syntax error inside, but DEBUG is undefined, does the build fail?
No — the block's text is deleted before the compiler sees it, so a syntax error hidden in dead conditional code goes unnoticed until someone actually defines
DEBUG.Is whitespace allowed between # and the directive name, as in # define X 1?
Yes — spaces and tabs between
# and the keyword are legal and ignored. The # just has to be the first non-blank token on the line.Recall One-line summary to lock it in
Every trap on this page is the same trap wearing costumes: the preprocessor edits text and understands nothing else — no types, no values, no scope, no runtime. Reason from that and you can re-derive each answer.
Related deep dives worth revisiting: Macros vs inline functions and Conditional compilation for cross-platform code.