Skip to content

The C Preprocessor

New Course Coming Soon:

Get Really Good at Git

How to use the C Preprocessor

The preprocessor is a tool that helps us a lot when programming with C. It is part of the C Standard, just like the language, the compiler and the standard library.

It parses our program and makes sure that the compiler gets all the things it needs before going on with the process.

What does it do, in practice?

For example, it looks up all the header files you include with the #include directive.

It also looks at every constant you defined using #define and substitutes it with its actual value.

That’s just the start, and I mentioned those 2 operations because they are the most common ones. The preprocessor can do a lot more.

Did you notice #include and #define have a # at the beginning? That’s common to all the preprocessor directives. If a line starts with #, that’s taken care by the preprocessor.

Conditionals

One of the things we can do is to use conditionals to change how our program will be compiled, depending on the value of an expression.

For example we can check if the DEBUG constant is 0:

#include <stdio.h>

const int DEBUG = 0;

int main(void) {
#if DEBUG == 0
  printf("I am NOT debugging\n");
#else
  printf("I am debugging\n");
#endif
}

Symbolic constants

We can define a symbolic constant:

#define VALUE 1
#define PI 3.14
#define NAME "Flavio"

When we use NAME or PI or VALUE in our program, the preprocessor replaces its name with the value, before executing the program.

Symbolic constants are very useful because we can give names to values without creating variables at compilation time.

Macros

With #define we can also define a macro. The difference between a macro and a symbolic constant is that a macro can accept an argument and typically contains code, while a symbolic constant is a value:

#define POWER(x) ((x) * (x))

Notice the parentheses around the arguments, a good practice to avoid issues when the macro is replaced in the precompilation process.

Then we can use it in our code like this:

printf("%u\n", POWER(4)); //16

The big difference with functions is that macros do not specify the type of their arguments or return values, which might be handy in some cases.

If defined

We can check if a symbolic constant or a macro is defined using #ifdef:

#include <stdio.h>
#define VALUE 1

int main(void) {
#ifdef VALUE
  printf("Value is defined\n");
#else
  printf("Value is not defined\n");
#endif
}

We also have #ifndef to check for the opposite (macro not defined).

We can also use #if defined and #if !defined to do the same task.

It’s common to wrap some block of code into a block like this:

#if 0

#endif

to temporarily prevent it to run, or to use a DEBUG symbolic constant:

#define DEBUG 0

#if DEBUG
  //code only sent to the compiler
  //if DEBUG is not 0
#endif

Predefined symbolic constants you can use

The preprocessor also defines a number of symbolic constants you can use, identified by the 2 underscores before and after the name, including:

Are you intimidated by Git? Can’t figure out merge vs rebase? Are you afraid of screwing up something any time you have to do something in Git? Do you rely on ChatGPT or random people’s answer on StackOverflow to fix your problems? Your coworkers are tired of explaining Git to you all the time? Git is something we all need to use, but few of us really master it. I created this course to improve your Git (and GitHub) knowledge at a radical level. A course that helps you feel less frustrated with Git. Launching Summer 2024. Join the waiting list!
→ Get my C Handbook

Here is how can I help you: