First, there are quite a few preprocessor directives out there (14 in total I believe) with #include winning the popularity contest. Others that are used often are #pragma and #ifndef and all of its family .Let's take a look at pragma:
Each implementation of C and C++ supports feature unique to its host machine or operating system. Various programs/applications "must" exert quite a bit of control over their memory allocation and function parameters. The #pragma directive is a way to offer each compiler machine and/or operating system specific features while (attempting ) to maintain overall compatibility with the C and C++ languages. OK! Enough definition let's see some examples!
My favorite use of pragma is to avoid annoying linker problems with the following code segment:
#pragma comment (lib, "winmm.lib") //example
This has the compiler directly insert that library into the code in case (for whatever reason) you were having problems linking it. It places a library-search record in the object file. This comment type must be accompanied by a commentstring parameter containing the name (and possibly the path) of the library that you want the linker to search.
Overall the pragma portion has some versatility, as it can be used in conditional statmements such as #ifndef and all of its family . For C++ users the pragma comment has ~32 commands (C has 28 or so) that can be used.
Ever want a faster build time? Good news is that pragma has a directive just for you:
//some header.h file #pragma once
This directive specifies that the file will be included (opened) only once by the compiler in a build. This can reduce build times as the compiler will not open and read the file after the first #include of the module. However, it is worth noting that #pragma directives are compiler specific and will vary depending on what you are using (I’m currently using MSVS 2005). If the compiler does not support a specific argument for #pragma, it is ignored - no error is generated.
Let’s discuss the range of #if… statements:
#define #ifdef #ifndef #endif #undef #elif #else #if !defined …etc…
These directives allow to include or discard part of the code of a program if a certain condition is met.
#ifdef allows a section of a program to be compiled only if the macro that is specified as the parameter has been defined, no matter which its value is. For example:
#ifdef ARRAY _SIZE int array[ARRAY _SIZE]; #endif
In this case, the line of code int array[ARRAY_SIZE]; is only compiled if ARRAY_SIZE was previously defined with #define, independently of its value. If it was not defined, that line will not be included in the program compilation.
#ifndef serves for the exact opposite: the code between #ifndef and #endif directives is only compiled if the specified identifier has not been previously defined. For example:
#ifndef ARRAY _SIZE #define ARRAY _SIZE 100 #endif int array [ARRAY _SIZE];
In this case, if when arriving at this piece of code, the ARRAY_SIZE macro has not been defined yet, it would be defined to a value of 100. If it already existed it would keep its previous value since the #define directive would not be executed.
The #if, #else and #elif (i.e., "else if") directives serve to specify some condition to be met in order for the portion of code they surround to be compiled. The condition that follows #if or #elif can only evaluate constant expressions, including macro expressions. For example:
#if ARRAY _SIZE>200 #undef ARRAY _SIZE #define ARRAY _SIZE 200 #elif ARRAY _SIZE<50 #undef ARRAY _SIZE #define ARRAY _SIZE 50 #else #undef ARRAY _SIZE #define ARRAY _SIZE 100 #endif int array [ARRAY _SIZE];
Notice how the whole structure of #if, #elif and #else chained directives ends with #endif.
The behavior of #ifdef and #ifndef can also be achieved by using the special operators defined and !defined respectively in any #if or #elif directive:
#if !defined TABLE_SIZE #define TABLE_SIZE 100 #elif defined ARRAY_SIZE #define TABLE_SIZE ARRAY_SIZE int table[TABLE_SIZE];
Line control (#line)
When we compile a program and some error(s) happen during the compiling process, the compiler shows an error message with references to the name of the file where the error happened and a line number, so it is easier to find the code generating the error.
The #line directive allows us to control both things, the line numbers within the code files as well as the file name that we want that appears when an error takes place. Its format is:
#line number "filename"
Number is the new line number that will be assigned to the next code line. The line numbers of successive lines will be increased one by one from this point on.
"filename" is an optional parameter that allows to redefine the file name that will be shown. For example:
#line 20 "assigning variable" int a?;
This code will generate an error that will be shown as error in file "assigning variable", line 20.
Error directive (#error)
This directive aborts the compilation process when it is found, generating a compilation the error that can be specified as its parameter:
#ifndef __dreamincode #error A C++ compiler is required! #endif
This example aborts the compilation process if the macro name __dreamincode is not defined.
With all that knowledge under our proverbial belt, let us move on to a powerful (perhaps underused?) feature of preprocessor directives -> macros. Yes, yes, we all know what macros are, but let's see the possible usefulness (AND DANGER) of one defined in a preprocessor directive:
// Macro to get a random integer with a specified range #define getrandom(min, max) \ ((rand()%(int)(((max) + 1)-(min)))+ (min)) //example taken from msdn
Here it is almost like a function call, but all included in the preprocess directive. Whenever getrandom() is called it is executed from this code. Here is another example of defining your own macro:
#define MAX(a, b) ( (a) > (b) ) ? (a) : (b) )
A classic function that returns a value based on which variable is greater. Again, it may not be in your best interest to decalre and use a directive such as that, but good to know the capability exists.
Using macros in this sort of fashion is not always recommended! The results can be unpredictable and crashing is likely (if not implemented properly). In fact, if there is more then one programmer using/modifying the code (i.e. anything that is not one of your personal projects), it would be better to use functions instead of a macro in the above case. These problems have led to several naming conventions among programmers, most notably the ALL CAPS with underscores to denote periods or spaces before the name. This is used when including .h files: #define __HELPER_H and when using macros: #define MAX, FUNCTION HERE!!! If multiple people go about doing the same task in a variety of ways, there will be problems. Unfortunately, it will be the kind that are small and annoying to catch (dangling pointers anyone?).
This use of #define is BAD PRACTICE!!!:
#define getmax(a, b) ( (a) > (b) ) ? (a) : (b) )
Unless explicitly identified, no one would guess that that is a macro… Here is the entire warning in a nutshell:
Because preprocessor replacements happen before any C++ syntax check, macro definitions can be a tricky feature, but be careful: code that relies heavily on complicated macros may result obscure to other programmers, since the syntax they expect is on many occasions different from the regular expressions programmers expect in C++.
Here’s a list outlining the following macros that are ‘constant’. (Notice the underscores and all caps convention, except for the last one).
The following macro names are defined at any time:
Macro -------- Value
__LINE__ ----- Integer value representing the current line in the source code file being compiled.
__FILE__ ----- A string literal containing the presumed name of the source file being compiled.
__DATE__ ----- A string literal in the form "Mmm dd yyyy" containing the date in which the compilation process began.
__TIME__ ----- A string literal in the form "hh:mm:ss" containing the time at which the compilation process began.
__cplusplus ----- An integer value. All C++ compilers have this constant defined to some value. If the compiler is fully
compliant with the C++ standard its value is equal or greater than 199711L depending on the version of the
standard they comply.
Why is this important to C and C++ programmers? Well, it may or may not be for a variety of reasons. If your project is massive, using directive such as #ifndef, #pragma once, etc... can save you hours of debugging time from twice defined identifers and bad function calls. Consider it a tool much like declaring a variable to be const. If the desired output is wrong then the function manipulating it must be in error rather then a changed variable that was uncaught. Hopefully this will aid you in a better understanding of how the preprocessor works. There is so much material; it can ebb and flow to your specific needs. Happy coding!