What is the C preprocessor?
The preprocessor runs before the compiler and works on text: it pastes in
#includefiles, expands macros, removes comments and keeps or drops code under#if. It knows nothing about types or scope, which is the root of every macro bug.gcc -Eshows its output. See the preprocessor.What does
SQUARE(2 + 3)give with#define SQUARE(x) x * x?C#include <stdio.h> #define SQUARE(x) x * x int main(void) { printf("%d\n", SQUARE(2 + 3)); return 0; }It prints
11. A macro substitutes text, soSQUARE(2 + 3)becomes2 + 3 * 2 + 3. Parenthesize every parameter and the whole body:#define SQUARE(x) ((x) * (x))That still evaluates
xtwice, soSQUARE(i++)is undefined behavior; astatic inlinefunction has neither problem. See macros.How many times is
iincremented inMAX(i++, j)?C#include <stdio.h> #define MAX(a, b) ((a) > (b) ? (a) : (b)) int main(void) { int i = 5, j = 3; int m = MAX(i++, j); printf("%d %d\n", m, i); return 0; }It prints
6 7. The expansion((i++) > (j) ? (i++) : (j))incrementsiin the condition and again in the chosen branch, which yields 6. It is defined, since?:has a sequence point after the condition, but a function would evaluatei++once.What is the difference between a macro and a function?
A macro is text substituted by the preprocessor: no type checking, arguments evaluated as many times as they appear, nothing for a debugger to step into. A
static inlinefunction is type-checked, evaluates each argument once and costs nothing when inlined, so prefer it. Only macros can stringify (#x), paste tokens (##) and use the caller's__FILE__and__LINE__.What is an include guard, and how is it different from
#pragma once?An include guard stops a header from being processed twice in one translation unit, which would redefine its types.
#ifndef LIST_H #define LIST_H typedef struct Node Node; Node *list_push(Node *head, int value); #endif /* LIST_H */#pragma oncedoes the same in one line; it is not standard C, but GCC, Clang and MSVC support it.What do the
#and##operators do in a macro?#paramturns a macro argument into a string literal (stringification);a ## bglues two tokens into one (token pasting).C#include <stdio.h> #define SHOW(expr) printf(#expr " = %d\n", (expr)) #define FIELD(name) int field_##name struct Config { FIELD(width); FIELD(height); }; int main(void) { struct Config c = {640, 480}; SHOW(c.field_width * c.field_height); SHOW(17 / 5); return 0; }It prints
c.field_width * c.field_height = 307200and17 / 5 = 3.assertuses#to print the failing expression.What is conditional compilation, and where is it used?
#if,#ifdefand#ifndefkeep or drop code before compiling: debug logging, platform-specific code, flags from the build (gcc -DDEBUG).C#include <stdio.h> #define DEBUG 1 #if DEBUG #define LOG(msg) printf("[%s:%d] %s\n", __func__, __LINE__, msg) #else #define LOG(msg) ((void) 0) #endif int main(void) { LOG("starting"); #ifdef _WIN32 printf("Windows build\n"); #else printf("not a Windows build\n"); #endif return 0; }It prints
[main:12] startingandnot a Windows build.__func__is a predefined identifier, not a macro, so#ifcannot test it.Why are multi-statement macros wrapped in
do { ... } while (0)?So the macro acts as one statement everywhere, including an
ifwithout braces, and still needs its semicolon.#define SWAP_BAD(a, b) int t = a; a = b; b = t; #define SWAP(a, b) do { int t_ = (a); (a) = (b); (b) = t_; } while (0) if (x > y) SWAP(x, y); /* the whole swap is the if body */ else puts("sorted");With
SWAP_BADonly the first statement belongs to theif. Plain braces fail too: in{...};the;ends theif, so theelsedoes not compile.What is the difference between
#defineandtypedef?typedefcreates a real alias for a type, which the compiler checks and scopes;#defineis text the preprocessor pastes in before compilation. The difference shows with pointers:C#include <stdio.h> #define INT_PTR int * typedef int *IntPtr; int main(void) { int x = 1, y = 2; INT_PTR a, b; /* expands to: int *a, b; so b is a plain int */ IntPtr c, d; /* both are int * */ a = &x; b = y; c = &x; d = &y; printf("%d %d %d %d\n", *a, b, *c, *d); return 0; }It prints
1 2 1 2, andb = ycompiles becauseINT_PTR a, b;becameint *a, b;, which makesba plainint. Atypedefname also follows block scope. Usetypedeffor types and keep#definefor constants and macros. See typedef.How do you write a macro that takes a variable number of arguments?
End the parameter list with
...and use__VA_ARGS__in the body (C99).C#include <stdio.h> #define LOG(level, ...) printf("[" level "] " __VA_ARGS__) int main(void) { LOG("INFO", "server started on port %d\n", 8080); LOG("WARN", "disk at %d%%\n", 91); LOG("INFO", "shutting down\n"); return 0; }It prints
[INFO] server started on port 8080,[WARN] disk at 91%and[INFO] shutting down. The format string is part of...on purpose: with#define LOG(fmt, ...) printf(fmt, __VA_ARGS__), a call with no extra arguments leaves a trailing comma. GCC's, ##__VA_ARGS__and C23's__VA_OPT__(,)remove that comma. When you do not need the caller's__LINE__, a variadic function built onvprintfis the better tool.
C Preprocessor Interview Questions
Macros, include guards and conditional compilation, with the macro traps that become output questions.