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
- Preprocessor directives are lines starting with the character
#
, e.g.#define FOO 107
- Whitespace is also allowed before and after the
#
symbol - Directives can expand over multiple lines by using the character
\
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
.
-
#include <file>:
include system header file
The preprocessor searches for file in system directories (see the directory seearch list:cpp -v
). -
#include "file"
: include header files of your own program
The preprocessor searches for file first in the current directory, then in the same directories used for system header files. -
#include
anything else
:
This is known as a computed #include directive where anything else can be other macros, which are expanded.
After the expansion, it must conform one of the first two variants:<file>
or"file"
.
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);
#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");
}
- Note that the C Preprocessor would also replace
#include <stdio.h>
accordingly.
Include Guards: Once-Only Include Files
You will most likely include a header file multiple times in a project.
- Compiling error occurs if a header file is included multiple times in the same file (variables/functions get defined multiple times)
- The FIX: Add Include Guards in the header file
: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");
}
- The macro
HELLO_H
indicates that the file has been included once already - If a subsequent
#include
specifies the same file, and the macro in the#ifndef
is already defined, then the contents within the directives are skipped
NOTE: You may see #pragma once
which is a non-standard macro.
- Widely used directive designed to cause the current source file to be included only once in a single compilation
#pragma once
is now obsolete and should not be used!
: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.
#define VAR value
: setsVAR
tovalue
:large_orange_diamond: Define Macro Expansion
#define BUFFER 128
char *str = (char *) malloc(BUFFER);
char *str = (char *) malloc(128);
:large_orange_diamond: Define Macro Top-Down Expansion
course = CS
#define CS 107
num = CS
course = CS
num = 107
#undef
: undefine a macro variable
#undef VAR
: unsetsVAR
:large_orange_diamond: Undefine Macro Example
#define CS 107
course = CS
#undef CS
num = CS
course = 107
num = CS
Macro Naming Conventions
- In user header files: the macro name should not begin with
_
- Avoid naming collisions with system header files which normally begin with
__
- Good practice: macro name contains file name and additional text to avoid conflicts with other header files
Bad User Macro Naming Examples:
#define _MYVAR_
#define __TESTVAR__
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
.
#if
Directive: Basic conditionals paired with#endif
.#else
Directive: Including some text if the condition fails.#elif
Directive: Testing several alternative possibilities.
: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
#ifdef VAR
#if defined VAR
#ifndef
: Check if a variable has not been defined
#ifndef VAR
#if !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.
#define
directive with a list of argument names in parentheses after the macro name- open-parenthesis must follow the macro name immediately (no space in between)
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))
- Example Usage:
MIN(2,3)
expands to2
- Example Usage:
MIN(cs + 107, *p)
expands to((cs + 107) < (*p) ? (cs + 107) : (*p))
: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
- (a) Write a function to take additional arguments without having to make a new function
- (b) Have the function change via compile-time defined variables on the command line
:large_orange_diamond: Deepnote: 3D Function Arguments
Some Useful System Macros
__FILE__
The name of the current file, as a string literal__LINE__
Current line of the source file, as a numeric literal__DATE__
Current system date, as a string__TIME__
Current system time, as a string__cplusplus
Undefined for C code compiled by a C compiler;199711L
C code is compiled by a C++ compiler (98 C++ standard)__func__
Current function name of the source file, as a string (part of C99)
: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.