Skip to content

The C Preprocessor

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:

  • __LINE__ translates to the current line in the source code file
  • __FILE__ translates to the name of the file
  • __DATE__ translates to the compilation date, in the Mmm gg aaaa format
  • __TIME__ translates to the compilation time, in the hh:mm:ss format
→ Download my free C Handbook!

THE VALLEY OF CODE

THE WEB DEVELOPER's MANUAL

You might be interested in those things I do:

  • Learn to code in THE VALLEY OF CODE, your your web development manual
  • Find a ton of Web Development projects to learn modern tech stacks in practice in THE VALLEY OF CODE PRO
  • I wrote 16 books for beginner software developers, DOWNLOAD THEM NOW
  • Every year I organize a hands-on cohort course coding BOOTCAMP to teach you how to build a complex, modern Web Application in practice (next edition February-March-April-May 2024)
  • Learn how to start a solopreneur business on the Internet with SOLO LAB (next edition in 2024)
  • Find me on X

Related posts that talk about clang: