Author: Greg Gutmann
Affiliation: Tokyo Institute of Technology
Introduction
As my CUDA tutorials progress it is beneficial to use more features of C and C++ within them. However, since not everyone learns the features of a language in the same order I thought it would be useful to make posts explaining some of the features that I will use.
Defining Macros in Files
We will start off with the simple case where preprocessor macros are only being defined at the top of the file.
#include <stdio.h> // printf
#define PI 3.14159265359 // double
#define PI 3.141592f // Specified as a float with f
#define HEIGHT 1000
#define WIDTH ( WIDTH * 2 )
#define addOne(val) val + 1
int main()
{
printf("PI is %f\n", PI);
printf("5 + 1 = %d\n", addOne(5));
return 0;
}
Compile Command:
gcc sample0.cpp -o sample0
Sample's Output:
PI is 3.141593
5 + 1 = 6
A times #define is used to declare a constant global value, such as PI, FPI and HEIGHT. #define can also compute its value based on a previous #define, such as with WIDTH.
Or it can be used to create a macro that does an operation such as addOne. However, I do not suggest using that example. If I pass 5.2 to the macro it returns a garbage result (-858993459), as it is not type-safe. Which shows one potential downside to the use of macros.
Good page on printf if it is new: http://www.cplusplus.com/reference/cstdio/printf/
Defining Macros at Command Line
Macros can be defined at compile time as well. Maybe the best example of this is DEBUG, which is often used to include or exclude the developer’s extra code for debugging.
#include <stdio.h> // printf
#define PI 3.14159265359
int main()
{
// Using a defined macro
#ifdef DEBUG
printf("DEBUG was defined at compile time\n");
#endif // DEBUG
printf("PI is %f\n", PI);
return 0;
}
Compile Command:
gcc sample1.cpp -o sample1
Sample's Output:
PI is 3.141593
If this second example is compiled like the first sample was, the result is similar to the first sample (except for the missing printf after PI).
Compile Command:
gcc -DDEBUG sample1.cpp -o sample1D
Sample's Output:
DEBUG was defined at compile time
PI is 3.141593
If -D is used to define the DEBUG macro at compile time, the #ifdef result is true, and the code that it encloses is compiled into the executable. Which means the code has also printed the line inclosed in #ifdef and #endif. Note: You can add a space after -D “-D DEBUG” and it will also work.
Defining Macros with Definitions
The macros can also be defined at compile time with a definition (value), for added flexibility.
#include <stdio.h> // printf
#define PI 3.14159265359
int main()
{
// Using a defined macro that has a definition set
#ifdef VERBOSE_LEVEL
#if VERBOSE_LEVEL == 1
printf("Currently running with verbose level %d\n", VERBOSE_LEVEL);
printf("PI is %lf\n", PI);
#endif
#if VERBOSE_LEVEL == 2
printf("Currently running with verbose level %d\n", VERBOSE_LEVEL);
printf("PI is %lf\n", PI);
printf("More Text\n");
#endif
#else
printf("bye\n");
#endif
return 0;
}
Compile Command:
gcc sample2.cpp -o sample2
Sample's Output:
bye
When no macros are defined at compile time it skips to the printf saying “bye”. Often if verbose or something similar was not defined the code would skip past and continue on to the next portion of the code. But there are cases where a #else will be used.
Compile Command:
gcc -DVERBOSE_LEVEL=1 sample2.cpp -o sample2V1
Sample's Output:
Currently running with verbose level 1
PI is 3.141593
This time VERBOSE_LEVEL was defined and given a definition of 1.
Compile Command:
gcc -DVERBOSE_LEVEL sample2.cpp -o sample2V
Sample's Output:
Currently running with verbose level 1
PI is 3.141593
This time VERBOSE_LEVEL was defined and not defined. However, when the definition is not set it defaults to 1 as shown in the documentation link here: https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html
Compile Command:
gcc -DVERBOSE_LEVEL=2 sample2.cpp -o sample2V2
Sample's Output:
Currently running with verbose level 2
PI is 3.141593
More Text
This time VERBOSE_LEVEL was defined and given a definition of 2.
Inclusive Example
The sample below contains most of the previous samples within it, showing many uses of macros can be all used at the same time.
#include <stdio.h> // printf
#define PI 3.14159265359
int main()
{
// Using a defined macro
#ifdef DEBUG
printf("DEBUG was defined at compile time\n");
#endif // DEBUG
// Using a defined macro that has a definition set
#ifdef VERBOSE_LEVEL
#if VERBOSE_LEVEL == 1
printf("Currently running with verbose level %d\n", VERBOSE_LEVEL);
printf("PI is %lf\n", PI);
#endif
#if VERBOSE_LEVEL == 2
printf("Currently running with verbose level %d\n", VERBOSE_LEVEL);
printf("PI is %lf\n", PI);
printf("More Text\n");
#endif
#else
printf("bye\n");
#endif
return 0;
}
Compile Command:
gcc -DDEBUG -DVERBOSE_LEVEL=2 sample3.cpp -o sample3DV2
Sample's Output:
DEBUG was defined at compile time
Currently running with verbose level 2
PI is 3.141593
More Text
Conclusion
From my perspective macros can be convenient to use as well as add more flexibility to code. For example, in CUDA if a code has been optimized for several different architectures, macros are often used to include only the portions of code that are relevant to the architecture specified at compile time.
Link to next post: Part 2.
Contact me if you would like to use the contents of this post. Thanks 🙂
Copyright © 2019 by Gregory Gutmann