Chapter 14 Summary and Quiz
Recap of the preprocessor.
Text Before Compilation
This chapter took the one-sentence description from chapter 1, "the preprocessor runs first", and made it precise: the preprocessor pastes text. It never evaluates an expression, never checks a type, never sees a variable — it edits your source before the compiler reads a single declaration. Every trap in the chapter followed from that fact, and every fix was a way of pasting text that stays correct no matter what surrounds it. Here is the whole chapter in one pass, then a cumulative quiz over both lessons.
Macros and File Inclusion
An object-like macro, #define PI 3.14159, names a piece of replacement text: no equals sign, no semicolon, because nothing is being assigned. A trailing semicolon becomes part of the replacement text and breaks the use sites, not the definition. The UPPER_CASE convention warns the reader that the name is not a variable, has no address, and obeys no scope rules.
A function-like macro puts a parenthesized parameter list immediately after the name — one stray space and #define F (x) is an object-like macro whose body is the tokens (x). The safe shape parenthesizes every parameter and the whole body:
#define SQUARE(x) ((x) * (x))
Both layers defend against a distinct trap:
- Unparenthesized parameter:
#define BAD_SQUARE(x) x * xturnsBAD_SQUARE(2 + 3)into2 + 3 * 2 + 3, which precedence makes 11, not 25. The argument was never "the value 5"; it was three tokens pasted verbatim. - Unparenthesized body:
#define HALF_SQUARE(x) (x) * (x)turns100 / HALF_SQUARE(5)into100 / (5) * (5), which left-to-right evaluation makes 100, not 4. The outer pair makes the expansion one indivisible operand.
Hence the rule applied to every macro from now on: parenthesize every parameter and the whole body, always.
Parenthesization cannot fix the third trap, because it is not about grouping. A macro pastes its argument's text into every place the parameter appears, so SQUARE(i++) expands to ((i++) * (i++)): two modifications of i with no sequence point between them, the unsequenced-modification rule from chapter 3 — undefined behaviour, whatever the program happens to print. Never pass an expression with a side effect as a macro argument; evaluate it into a plain variable first. The double evaluation also means an expensive argument runs twice.
Set against functions, macros have no type checking, no address (so no handing them to chapter 12's function pointers or qsort), and expand at every use site; what they buy is type flexibility, since one MAX2 serves int and double alike. The verdict stands: functions for logic; macros for named constants and conditional compilation.
#include is the other paste: the directive is replaced by the entire text of the named file. The two spellings differ only in where the preprocessor searches — <stdio.h> goes straight to the system directories; "myheader.h" tries the including file's directory first, then falls back to the system search. Angle brackets for standard headers, quotes for your own.
Conditional Compilation
Between #ifdef NAME and #endif, source survives only if NAME is currently defined as a macro; otherwise the region is deleted before the compiler sees it — absent from the program, not skipped at run time. #ifdef tests definedness, never value, so an empty #define DEBUG counts; #ifndef is the mirror image, and #else covers both outcomes.
The classic use is debug logging: wrap the printf calls in #ifdef DEBUG, and compile with gcc -DDEBUG to define the macro from the command line without editing the source. One file, two programs, chosen at build time — the same trick the standard itself blesses with NDEBUG and assert.
For more than one yes/no question, #if evaluates a full constant integer expression, defined(NAME) yields 1 or 0 inside it, so #if defined(DEBUG) && !defined(QUIET) combines tests no #ifdef can, and #elif chains further cases. The expression must be constant — no variables, no function calls, because it is evaluated before compilation. #undef NAME removes a definition, whatever its origin, after which #ifdef NAME is false again.
Include Guards
Textual paste has a failure mode: main.c includes geometry.h directly and through another header, the struct definition inside is pasted twice, and the compiler stops with error: redefinition of 'struct point'. A repeated declaration like extern int count; is harmless; a repeated definition is a hard error, and no one can hand-track fifty headers' include paths.
The fix is the chapter's most important pattern. Every header wraps its entire contents in three lines:
#ifndef GEOMETRY_POINT_H
#define GEOMETRY_POINT_H
/* the header's real contents */
#endif
First paste: the macro is undefined, so the contents survive, and the #define on the next line records that the header has been seen. Every later paste: the macro is defined, so #ifndef deletes everything down to #endif. Name the guard after the header's path to avoid collisions, never start it with an underscore-capital or double underscore (reserved for the implementation), and prefer this portable pattern over the non-standard #pragma once. The rule is absolute: every header you ever write gets a guard.
Macros You Never Defined
The implementation predefines macros using exactly the reserved names you may not: __STDC__ expands to 1 on every conforming ANSI compiler, __FILE__ to the current file name as a string, and __LINE__ to the current line number as an integer — the raw material of real logging. Compilers also predefine platform macros like _WIN32, which is how one codebase targets many systems: the excluded branches cost nothing forever after.
What Comes Next
Chapter 15, Writing Real Programs, is where the whole course converges. First comes structuring a larger program — top-down design, splitting work into functions, and the habits that keep a 300-line program readable — and then the course capstone, a complete record-management program that draws on every chapter, this one's macros and guards included. After that, you write C, not exercises.
Key Takeaways
- The preprocessor pastes text before compilation; every macro question is answered by writing out the expansion.
- Object-like macros take no equals sign and no semicolon; function-like macros need the
(hard against the name, and parenthesize every parameter and the whole body —BAD_SQUARE(2 + 3)is 11 and100 / HALF_SQUARE(5)is 100 without them. - Macros paste arguments into every parameter use, so
SQUARE(i++)modifiesitwice with no sequence point: undefined behaviour. Never pass side effects to macros. - Functions type-check, evaluate arguments once, and have addresses; macros trade all that for type flexibility. Functions for logic, macros for constants and conditional compilation.
#include <...>searches the system directories;#include "..."searches the including file's directory first. Both are pure textual paste.#ifdef/#ifndef/#else/#endiftest definedness and delete losing regions before compilation;-DNAMEdefines a macro from the compile line (theDEBUGlogging pattern);#iftakes a constant expression withdefined(),&&,!, and#elif;#undefremoves a definition.- Double inclusion pastes a header twice and redefines its structs, a hard error; the three-line include guard (
#ifndef GUARD/#define GUARD/ contents /#endif) makes every paste after the first vanish. Every header gets one. - The implementation predefines
__STDC__(1 on ANSI compilers),__FILE__, and__LINE__; reserved double-underscore names belong to it, not to you.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Chapter 14 Summary and Quiz - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!