Preprocessor & Command-Line Arguments
Before compiling, and before mainBefore the compiler sees your code, the preprocessor works through every line that starts with #: it pastes in header files, replaces macros and keeps or drops code with #ifdef. In the lab below you watch that text replacement happen — including a classic macro bug — and see how a program reads the words typed after its name with argc and argv.
- #definereplaces text before compiling
- #ifdefkeeps or removes code
- argvthe words typed after the program name
Ready
Keyboard: ← → step · Space play / pause · R reset · Esc exit classroom mode
The ideas behind the lab
Exactly what you watched happen, in plain steps.
From source file to program
Compiling starts with the preprocessor. It handles every line that begins with #: #include copies a header file in, #define creates macros that are replaced as plain text, and #if / #ifdef keep or drop blocks of code. Only the result goes to the compiler. When the finished program runs, main can receive the command line it was started with: argc is the number of words and argv an array of strings holding them.
#include <stdio.h> // paste in a header
#define MAX 100 // a constant: MAX → 100
#define SQUARE(x) ((x) * (x)) // a macro with a parameter
#ifdef DEBUG // only if DEBUG is defined
printf("x = %d\n", x);
#endif
int main(int argc, char *argv[]) // $ ./prog a b → argc = 3
| Directive | What it does | Example |
|---|---|---|
| #include <file> | paste a system header | #include <stdio.h> |
| #include "file" | paste your own header (searched first in your folder) | #include "shapes.h" |
| #define NAME text | replace NAME by text everywhere after it | #define SIZE 10 |
| #define F(x) text | a macro with parameters | #define MAX(a, b) ((a) > (b) ? (a) : (b)) |
| #undef NAME | forget a macro | #undef SIZE |
| #if / #ifdef / #ifndef / #else / #endif | conditional compilation | #ifdef DEBUG … #endif |
Macros
- #define replaces text before compiling — no type, no memory, no checks.
- Put brackets around every parameter and around the whole macro body.
- Never pass expressions with side effects: SQUARE(i++) increments twice.
- No semicolon at the end of a #define (it would be pasted in too).
- For typed constants, const int or enum is often the better choice.
Headers and conditional compilation
- A header (.h) holds prototypes, types and macros shared by several .c files.
- #ifdef NAME … #endif keeps the code only if NAME is defined.
- gcc -DDEBUG prog.c defines DEBUG from the command line.
- Header guards stop a header being included twice: #ifndef SHAPES_H / #define SHAPES_H / … / #endif.
- gcc -E prog.c shows the code after preprocessing.
argc and argv
- int main(int argc, char *argv[]) receives the command line.
- argv[0] is the program name; argv[1] … argv[argc − 1] are the arguments.
- argv[argc] is NULL.
- Every argument is a string: convert with atoi, atof or strtol.
- Check argc before using argv[i], and print a usage message if something is missing.
The largest of the numbers given on the command line
Every line, explained.
#include <stdio.h>
#include <stdlib.h>
For atoi.
#define MAX_ARGS 10
#define MAX(a, b) ((a) > (b) ? (a) : (b))
Brackets everywhere.
int main(int argc, char *argv[])
Run as: ./max 4 17 9
{
if (argc < 2) {
No numbers given?
printf("usage: %s numbers...\n", argv[0]);
return 1;
}
int best = atoi(argv[1]);
"4" → 4
for (int i = 2; i < argc && i <= MAX_ARGS; i++)
best = MAX(best, atoi(argv[i]));
Expands to ((best) > (atoi(argv[i])) ? … )
#ifdef DEBUG
Compiled only with gcc -DDEBUG.
printf("checked %d arguments\n", argc - 1);
#endif
printf("largest: %d\n", best);
Prints largest: 17
return 0;
}
Spot the bug
Mistakes almost every beginner makes — and how to fix them.
Macro without brackets
#define SQUARE(x) x * x SQUARE(a + 1) // a + 1 * a + 1
#define SQUARE(x) ((x) * (x)) SQUARE(a + 1) // ((a + 1) * (a + 1))
Macros paste text; operator precedence then splits the argument.
A semicolon after #define
#define SIZE 10; int a[SIZE]; // int a[10;];
#define SIZE 10 int a[SIZE];
Everything after the name is pasted, the semicolon included.
Side effects in macro arguments
int y = SQUARE(i++); // i is incremented twice
int y = SQUARE(i); i++;
The argument appears twice in the expansion, so it is evaluated twice.
Using argv without checking argc
int n = atoi(argv[1]); // crash if no argument
if (argc < 2) { printf("usage: …\n"); return 1; }
int n = atoi(argv[1]);
argv[1] does not exist (it is NULL) when the program is started without arguments.
Treating an argument as a number
int n = argv[1]; // an address, not 42
int n = atoi(argv[1]);
Every argument is a string; convert it first.
Check your understanding
Answer each question — you will see the explanation straight away.
When does the preprocessor run?
It prepares the text the compiler then translates.
#define DOUBLE(x) x + x — what is 3 * DOUBLE(2)?
3 * 2 + 2 = 8. With ((x) + (x)) it would be 12.
The program is run as ./prog red blue. What is argc?
The program name counts too.
The program is run as ./prog red blue. What is argv[1]?
argv[0] is the program name.
What is the purpose of #ifndef X_H / #define X_H / … / #endif in a header?
This is a header guard (include guard).
Preprocessor & Command-Line Arguments quiz
Test yourself: 10 questions, 70% to pass. Create a free account to take the quiz, save your progress and work towards the certificate.