C/C++ Preprocessor

Key Word(s): C++, Preprocessor




Key: :large_orange_diamond: - Code Example :large_blue_diamond: - Code Exercise :red_circle: - Code Warning
Previous: Source & Header Files
Next: C Data Types


C Preprocessor (cpp)

The C Preprocessor is a macro processor which allows you to define macros (abbreviations).
It is used automatically by the C compiler to transform your program before actual compilation.

The C Preprocessor can perform multiple functions:
- Include header files: files of declarations that can be substituted into your program
- Define macro expansions: abbreviations that are expanded with their definition in line
- Conditional compilation: include or exclude specific definitions using conditionals

Preprocessor Directives

Example:

:large_orange_diamond: Macro Define: cubeThreads

#define cubeThreads              \
  for (int k = 0; k < 4; ++k)    \
    for (int j = 0; j < 3; ++j)  \
      for (int i = 0; i < 6; ++i)

:large_orange_diamond: Usage: cubeThreads

cubeThreads {
  my_array[k][j][i] = 2*i + j + 3*k;
}


#include

Both user and system header files are included using the preprocessing directive #include.

How #include Works

Looking at our Hello, World! example, the C Proprocessor generates the following from hello_1.h to hello_1.c:

:large_orange_diamond: Before Pre-Processing

**hello_1.h**

void print_hello_world(void);
**hello_1.c**
#include <stdio.h>
#include "hello_1.h"

int main(void){
  print_hello_world();
  return 0;
}

void print_hello_world(void){
  printf("Hello, World!\n");
}

:large_orange_diamond: After Pre-Processing

#include <stdio.h>
void print_hello_world(void);

int main(void){
  print_hello_world();
  return 0;
}

void print_hello_world(void){
  printf("Hello, world!\n");
}


Include Guards: Once-Only Include Files

You will most likely include a header file multiple times in a project.

:large_orange_diamond: print_hello_world.h

// include guards
#ifndef HELLO_H
#define HELLO_H

/* system header files */
#include <stdio.h>

/* function declarations */
void print_hello_world(void);

#endif /* HELLO_H */

:large_orange_diamond: print_hello_world.c

/* header files */
#include "print_hello_world.h"
#include "print_hello_world.h" // include 2nd time!

int main(){
  print_hello_world();
  return 0;
}

void print_hello_world(void){
  printf("Hello, World!\n");
}

NOTE: You may see #pragma once which is a non-standard macro.

:large_orange_diamond: Deepnote: Header File Collision


Simple Macros

#define: define a variable

As we have seen, we can make a macro that serves as a substitution or abbreviation.
Before we can use the macro, it must first be defined with the #define directive.

:large_orange_diamond: Define Macro Expansion

#define BUFFER 128
char *str = (char *) malloc(BUFFER);
**Expanded result:**
char *str = (char *) malloc(128);

:large_orange_diamond: Define Macro Top-Down Expansion

course = CS
#define CS 107
num = CS
**Expanded result:**
course = CS
num = 107


#undef: undefine a macro variable

:large_orange_diamond: Undefine Macro Example

#define CS 107
course = CS
#undef CS
num = CS
**Expanded result:**
course = 107
num = CS


Macro Naming Conventions

Bad User Macro Naming Examples:


Conditional Macros

A conditional in the C preprocessor begins with a conditional directive: #if, #ifdef or #ifndef.
These conditional macros need to be closed with the directive #endif.

:large_orange_diamond: Conditional Macro Example

#if X == 1
...
#else /* X != 1 */
#  if X == 2
...
#  else /* X != 2 */
...
#  endif
#endif


#ifdef: Check if a variable has been defined using two directives


#ifndef: Check if a variable has not been defined


Macros With Arguments

Macros can be more flexible when they accept arguments.
Arguments are fragments of code that you supply each time the macro is used.
These fragments are included in the expansion of the macro according to the directions in the macro definition.

A macro that accepts arguments is called a function-like macro because the syntax for using it looks like a function call.

For example, here is a macro that computes the minimum of two numeric values, as it is defined in many C programs:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))


:red_circle: Macros With Arguments: Unintended Consequences :red_circle:

We can have unintended side effects using macros for function-like expressions.
For example, if we defined a MAX function as follows:

:red_circle: Buggy Macro Example: Define

#define MAX(a,b) a > b ? a : b

:red_circle: Buggy Macro Example: Usage

i = MAX(2,3)+5;
j = MAX(3,2)+5;

:red_circle: Buggy Macro Example: Expansion

i = 2 > 3 ? 2 : 3+5;
j = 3 > 2 ? 3 : 2+5;

:red_circle: Buggy Macro Example: Result

i = 8
j = 3

Be sure to use extra parenthesis for these scenarios!
If caution is taken, macros with arguments can be combined to make useful programming options.


Exercise: Macros With Arguments

Build a program that depends on physical space dimensions: 2D or 3D

:large_orange_diamond: Deepnote: 3D Function Arguments


Some Useful System Macros

:large_orange_diamond: Deepnote: System Macros


C Preprocessor Exercises

:large_blue_diamond: Deepnote: Fix Max Define Bug
:large_blue_diamond: Build a program that uses float when the macro SINGLE_PRECISION is defined, otherwise uses double as the floating pointing representation by defining a macro.


Next: C Data Types