Translation and the preprocessor
When you compile your code, you might expect that the compiler compiles the code exactly as you’ve written it. This actually isn’t the case.
Prior to compilation, the code file goes through a phase known as translation. Many things happen in the translation phase to get your code ready to be compiled (if you’re curious, you can find a list of translation phases here).
The most noteworthy of the translation phases involves the preprocessor. The preprocessor is best thought of as a separate program that manipulates the text in each code file.
When the preprocessor runs, it scans through the code file (from top to bottom), looking for preprocessor directives. Preprocessor directives (often just called directives) are instructions that start with a # symbol and end with a newline (NOT a semicolon). These directives tell the preprocessor to perform specific particular text manipulation tasks. Note that the preprocessor is not smart -- it does not understand C++ syntax.
The output of the preprocessor goes through several more translation phases, and then is compiled. Note that the preprocessor does not modify the original code files in any way -- rather, all text changes made by the preprocessor happen temporarily in-memory each time the code file is compiled.
In this lesson, we’ll discuss what some of the most common preprocessor directives do.
Includes
You’ve already seen the #include directive in action (generally to #include <iostream>). When you #include a file, the preprocessor replaces the #include directive with the preprocessed contents of the included file, which is then compiled.
Consider the following program:
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::cout << "Hello, world!"; return 0; } |
When the preprocessor runs on this program, the preprocessor will replace #include <iostream>
with the preprocessed contents of the file named “iostream”.
Since #include is used to almost exclusively used to include header files, we’ll discuss #include in more detail in the next lesson (when we discuss header files in more detail).
Macro defines
The #define directive can be used to create a macro. In C++, a macro is a rule that defines how input text is converted into replacement output text.
There are two basic types of macros: object-like macros, and function-like macros.
Function-like macros act like functions, and serve a similar purpose. We will not discuss them here, because their use is generally considered dangerous, and almost anything they can do can be done by a normal function.
Object-like macros can be defined in one of two ways:
#define identifier #define identifier substitution_text
The top definition has no substitution text, whereas the bottom one does. Because these are preprocessor directives (not statements), note that neither form ends with a semicolon.
Object-like macros with substitution text
When the preprocessor encounters this directive, any further occurrence of the identifier is replaced by substitution_text. The identifier is traditionally typed in all capital letters, using underscores to represent spaces.
Consider the following program:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> #define MY_NAME "Alex" int main() { std::cout << "My name is: " << MY_NAME; return 0; } |
The preprocessor converts the above into the following:
1 2 3 4 5 6 7 8 |
// The contents of iostream are inserted here int main() { std::cout << "My name is: " << "Alex"; return 0; } |
Which, when run, prints the output My name is: Alex
.
We recommend avoiding these kinds of macros altogether, as there are better ways to do this kind of thing. We discuss this more in lesson 4.14 -- Const, constexpr, and symbolic constants.
Object-like macros without substitution text
Object-like macros can also be defined without substitution text.
For example:
1 |
#define USE_YEN |
Macros of this form work like you might expect: any further occurrence of the identifier is removed and replaced by nothing!
This might seem pretty useless, and it is useless for doing text substitution. However, that’s not what this form of the directive is generally used for. We’ll discuss the uses of this form in just a moment.
Unlike object-like macros with substitution text, macros of this form are generally considered acceptable to use.
Conditional compilation
The conditional compilation preprocessor directives allow you to specify under what conditions something will or won’t compile. There are quite a few different conditional compilation directives, but we’ll only cover the three that are used by far the most here: #ifdef, #ifndef, and #endif.
The #ifdef preprocessor directive allow the preprocessor to check whether an identifier has been previously #defined. If so, the code between the #ifdef and matching #endif is compiled. If not, the code is ignored.
Consider the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> #define PRINT_JOE int main() { #ifdef PRINT_JOE // if PRINT_JOE is defined std::cout << "Joe\n"; // execute this code #endif #ifdef PRINT_BOB // if PRINT_BOB is defined std::cout << "Bob\n"; // execute this code #endif return 0; } |
Because PRINT_JOE has been #defined, the line cout << "Joe" << endl;
will be compiled. Because PRINT_BOB has not been #defined, the line cout << "Bob" << endl;
will be ignored.
#ifndef is the opposite of #ifdef, in that it allows you to check whether an identifier has NOT been #defined yet.
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { #ifndef PRINT_BOB std::cout << "Bob\n"; #endif return 0; } |
This program prints “Bob”, because PRINT_BOB was never #defined.
Object-like macros don’t affect other preprocessor directives
Now you might be wondering:
1 2 3 4 |
#define PRINT_JOE #ifdef PRINT_JOE // ... |
Since we defined PRINT_JOE to be nothing, how come the preprocessor didn’t replace PRINT_JOE in #ifdef PRINT_JOE with nothing?
Macros only cause text substitution for normal code. Other preprocessor commands are ignored. Consequently, the PRINT_JOE in #ifdef PRINT_JOE is left alone.
For example:
1 2 3 4 5 |
#define FOO 9 // Here's a macro substitution #ifdef FOO // This FOO does not get replaced because it’s part of another preprocessor directive std::cout << FOO; // This FOO gets replaced with 9 because it's part of the normal code #endif |
In actuality, the output of the preprocessor contains no directives at all -- they are all resolved/stripped out before compilation, because the compiler wouldn’t know what to do with them.
The scope of defines
Directives are resolved before compilation, from top to bottom on a file-by-file basis.
Consider the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> void foo() { #define MY_NAME "Alex" } int main() { std::cout << "My name is: " << MY_NAME; return 0; } |
Even though it looks like #define MY_NAME “Alex” is defined inside function foo, the preprocessor won’t notice, as it doesn’t understand C++ concepts like functions. Therefore, this program behaves identically to one where #define MY_NAME “Alex” was defined either before or immediately after function foo. For general readability, you’ll generally want to #define identifiers outside of functions.
Once the preprocessor has finished, all defined identifiers from that file are discarded. This means that directives are only valid from the point of definition to the end of the file in which they are defined. Directives defined in one code file do not have impact on other code files in the same project.
Consider the following example:
function.cpp:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> void doSomething() { #ifdef PRINT std::cout << "Printing!"; #endif #ifndef PRINT std::cout << "Not printing!"; #endif } |
main.cpp:
1 2 3 4 5 6 7 8 9 10 |
void doSomething(); // forward declaration for function doSomething() #define PRINT int main() { doSomething(); return 0; } |
The above program will print:
Not printing!
Even though PRINT was defined in main.cpp, that doesn’t have any impact on any of the code in function.cpp (PRINT is only defined from the point of definition to the end of function.cpp). This will be of consequence when we discuss header guards in a future lesson.
![]() |
![]() |
![]() |
I really wish this first introduction to the preprocessor would not show examples of using a macro to define a constant. We want new programmers in C++ to learn good habits from the beginning and they are just going to be confused when we show them creating constants with macros and then tell them not to do that. The tutorials here should always represent the best way to do something in C++.
The best uses for macros have been severely narrowed for a long time in C++ and we shouldn't show them bad habits.
You confused me already with that comment.
Thing is, I don't even know what I'm confused about and this is really confusing.
When creating header files, you can add them to the "source files" folder, or the "header files" folder (by right-clicking on the respective folders). Other than organization (in that all the header files should be in the "header files" folder), is there any reason to choose one or the other? The first question that comes to my mind is whether it affects the speed of the program in any way. (Or even if so, would it only affect the speed of compiling, and not of running the program?)
Thanks so much for these tutorials. Wish I had found them years ago. So many basic things that I needed to understand the C++ examples I was reading at the time.
You present at a good level of detail--enough examples, but not too long.
I believe that [ #pragma once ] serves the same purpose as header guards
#include directives like these are also valid:
#include "dir/file.h"
#include "../file.h"
I'm failing to see why I need to use header guards with the IDE that I'm using.
The code below I've used in Microsoft Visual C++ 2010 Express and it compiles without any complaints or errors.
NumericOperations.h
int add(int x, int y);
int subtract(int x, int y);
PrintAdd.h
#include "NumericOperations.h"
#ifndef PRINTADD_H
#define PRINTADD_H
void PrintAdd(int x, int y);
#endif
PrintSubtract.h
#include "NumericOperations.h"
#ifndef PRINTSUBTRACT_H
#define PRINTSUBTRACT_H
void PrintSubtract(int x, int y);
#endif
HeaderGuardTest.cpp
#include "stdafx.h"
#include "PrintAdd.h"
#include "PrintSubtract.h"
#include "KeyFunctions.h"
I should note that all the functions above have been defined in .cpp files.
My code compiles successfully, but I have included NumericOperations.h twice. Shouldn't this give me an error?
The #ifndef ensures that everything required is initialized once and no more. When you load it the second time, the compiler sees that the variables and whatnot have been declared, so it does nothing.
At this step
#define MY_NAME "Alex"
cout << "Hello, " << MY_NAME << endl;
i get 2 errors:
error C2143: syntax error : missing ';' before '<<'
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
what's wrong? I did something wrong, or was something wrong?
Simply well explained tutorial. Thanks a lot.
I just spotted a small typo here:
#Include tells the preprocessor......
Shouldn't #Include be #include with lowercase "i"?