C course menu
The C Preprocessor: #include, #define, Macros and Conditional Compilation
Understand the C preprocessor: how #include works, object-like and function-like macros, why macro arguments need parentheses, multi-statement macros with do-while(0), the # and ## operators, predefined macros, conditional compilation with #if and #ifdef, and include guards.
On this page
Before the compiler sees your code, the preprocessor runs over it. It handles every line that starts with #: it pastes in header files, replaces macros, and includes or removes code depending on conditions. It works on text, without knowing C's types or rules.
You can see its output with gcc -E file.c.
#include#
#include copies another file's contents into this one:
#include <stdio.h>searches the system include directories (standard and installed libraries).#include "mylib.h"searches the current file's directory first (your own headers).
Object-like macros#
#define NAME replacement replaces every later occurrence of NAME with the replacement text:
#include <stdio.h>
#define MAX_STUDENTS 60
#define GST_RATE 0.18
#define APP_NAME "Laviyx Learn"
int main(void) {
int seats[MAX_STUDENTS] = {0};
printf("%s: %zu seats, GST %.0f%%\n", APP_NAME, sizeof seats / sizeof seats[0], GST_RATE * 100);
return 0;
}Laviyx Learn: 60 seats, GST 18%By convention, macro names are in capitals. For typed constants, const variables and enum are often better choices (Constants).
Function-like macros#
Macros can take arguments, but they are text substitution, not function calls. Without parentheses, operator precedence produces surprising results:
#include <stdio.h>
#define SQUARE_BAD(x) x * x
#define SQUARE(x) ((x) * (x))
int main(void) {
printf("%d\n", SQUARE_BAD(2 + 3)); /* 2 + 3 * 2 + 3 */
printf("%d\n", SQUARE(2 + 3)); /* ((2 + 3) * (2 + 3)) */
return 0;
}11
25Rule: put parentheses around every use of a parameter and around the whole body.
Even with parentheses, an argument may be evaluated more than once:
#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main(void) {
int i = 5, j = 3;
int m = MAX(i++, j); /* i++ runs twice */
printf("m = %d, i = %d\n", m, i);
return 0;
}m = 6, i = 7MAX(i++, j) expands to ((i++) > (j) ? (i++) : (j)). For anything non-trivial, prefer a static inline function, which is type-checked and evaluates each argument once.
Multi-statement macros#
Wrap a macro that contains several statements in do { ... } while (0), so it behaves like one statement, even inside an if without braces:
#include <stdio.h>
#define SWAP_INT(a, b) do { int tmp_ = (a); (a) = (b); (b) = tmp_; } while (0)
int main(void) {
int x = 1, y = 2;
if (x < y)
SWAP_INT(x, y);
else
printf("already ordered\n");
printf("%d %d\n", x, y);
return 0;
}2 1# and#
Inside a macro, #param turns an argument into a string literal, and a ## b joins two tokens into one:
#include <stdio.h>
#define SHOW(expr) printf(#expr " = %d\n", (expr))
#define MAKE_VAR(name, n) name ## n
int main(void) {
int MAKE_VAR(score, 1) = 40; /* declares score1 */
SHOW(score1 * 2 + 1);
return 0;
}score1 * 2 + 1 = 81Predefined macros#
The compiler defines some macros for you:
#include <stdio.h>
int main(void) {
printf("line %d of %s\n", __LINE__, __func__);
printf("C standard: %ld\n", __STDC_VERSION__);
return 0;
}line 4 of main
C standard: 201710__FILE__, __LINE__, __DATE__ and __TIME__ are macros; __func__ is technically a predefined variable holding the function's name. __STDC_VERSION__ is 201710L when compiling as C17. Logging and assert use these to report where something happened.
Conditional compilation#
#if, #ifdef, #ifndef, #elif, #else and #endif include or remove code before compilation. Common uses are debug-only code and platform-specific code:
#include <stdio.h>
#define DEBUG 1
int main(void) {
#if DEBUG
printf("[debug] starting\n");
#endif
#ifdef _WIN32
const char *os = "Windows";
#elif defined(__linux__)
const char *os = "Linux";
#else
const char *os = "another system";
#endif
printf("compiled for %s\n", os);
return 0;
}[debug] starting
compiled for WindowsThis output is from a Windows build; on Linux the second line says Linux. You can also define macros on the command line instead of in the code: gcc -DDEBUG=1 main.c.
Include guards#
If a header is included twice in one file, directly or through other headers, its definitions are duplicated and the compiler reports errors. An include guard makes the second inclusion empty:
#ifndef STUDENT_H
#define STUDENT_H
typedef struct {
int roll;
char name[32];
} Student;
void print_student(const Student *s);
#endif /* STUDENT_H */Many compilers also support #pragma once, which is shorter but not part of the C standard. See Header Files and Multi-file Programs.
Practice#
Practice exercise
Write a macro ARRAY_LEN(a) that gives the number of elements in an array, and a macro CLAMP(x, lo, hi) that limits x to the range lo to hi. Use them on int v[] = {-5, 3, 12, 7}; to print each value clamped to 0..10.
Show solution
#include <stdio.h>
#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0])
#define CLAMP(x, lo, hi) ((x) < (lo) ? (lo) : (x) > (hi) ? (hi) : (x))
int main(void) {
int v[] = {-5, 3, 12, 7};
for (size_t i = 0; i < ARRAY_LEN(v); i++) printf("%d ", CLAMP(v[i], 0, 10));
printf("\n");
return 0;
}0 3 10 7 ARRAY_LEN only works on real arrays, not on pointers or array parameters.
Key takeaway#
Finished this lesson?