The C preprocessor is a heritage from an ancient age (the 70′s). Modern languages provide better ways to do most things the C preprocessor was (and is) used for (the D programming language has removed the need for the preprocessor with “normal” import
statements, support for conditional compilation using static if
statements etc.), but in C you’re stuck with the preprocessor, however, it isn’t that bad and, as a matter of fact, it can do some rather neat things.
Remember that even though the C preprocessor has many valid uses, it can probably be abused in more ways than so, leading to weird errors or code that is hard to understand. The preprocessor has its uses, but often, you should simply just avoid it.
#include
can include any kind of text, not just source code
The #include
statement includes any kind of text, not just source code. For instance, you can use #include
to include values for an array, assuming they are formatted as valid C code within a text file, e.g. 1, 2, 3, 4, 5
. If this was stored in a file called values.txt
, we could declare an array with these values simply by typing in the following in our C program:
int[] arr={ #include "values.txt" };
For larger sets of data, this could come in very handy.
Put more complex #define
values inside parenthesis
This is not okay:
#define TWO 1+1
First of all, you already have a constant for the value 2, it looks like this: 2
. Additionally, you should not define it as 1+1
, but instead simply as 2
. But disregarding all those flaws, there is yet another kind of problem. If we try to multiply our TWO
with say 3, we will get 4 as the answer, even though 2 times 3 is 6. Why? Because C changes 3*TWO
to 3*1+1
and multiplication has higher precedence than addition. This is solved simply by adding parenthesis around the value in the definition.
This is an example of those funny bugs careless use of the preprocessor can cause.
Retrieving the name of a variable
By putting a hash (#
) in front of a variable in a macro, you can add the identifier as a string literal into your code. The following is a macro that prints the name of a variable coupled with its value, which could be useful while debugging:
#define PRINT_VAL(val) printf("%s=%d\n", #val, val)
Note that the above example will only work with integers because of how printf()
is called.
#error
and #warning
You can use the #error
and #warning
directives to create your own compiler errors and warnings. For instance, if you would like to raise an error if someone tries to compile your program on Windows, you could add this little snippet of code:
#ifdef __WIN32__ #error "Disturbing, your choice of operating system is." #endif
You should only do this if you have a valid reason (e.g. don’t compile on Linux if you use DirectX, since Linux doesn’t support that), I guess, even though the example above could arguably be a valid reason, depending on whom you ask.
#warning
is used in the same way, though it will only cause the compiler to print a warning instead of the more severe error, which will halt compilation whereas a warning won’t. You could, for instance, set the compiler to print warnings whenever you are compiling with debug mode on, to avoid accidentally shipping the debugging version as production software.
Debugging preprocessor macros
Most compilers are able to do only the preprocessing step of the build, allowing you to see how preprocessor macros are expanded in your code, greatly easing the hunt for bugs related to the use of the preprocessor in the code. For GCC, the option to toggle this is -E
.
Compiler specific preprocessor features with #pragma
The C standard includes the #pragma
preprocessor statement for compilers to define their own preprocessor features. To see which pragma directives are provided by your compiler of choice, you should consult its documentation.
Most modern compilers support the #pragma once
statement. This will tell the compiler to include this file only one, removing the need for (the much more verbose) header guards commonly used. Still, the traditional header guards have been used for so long that programmers probably won’t stop using them any time soon, even if this alternative would generally be superior (some legacy compilers may not support this, though).
__FILE__
and __LINE__
__FILE__
will insert the name of the current file, and __LINE__
will insert the the line number of the current line of the source code. These can be very useful for generating debug information, especially if you are unable to use a debugger (think kernel programming (technically, it is possible to use a debugger for OS kernels, but it isn’t as easy and practical as it is to use one for user-space programming)).
__DATE__
and __TIME__
__DATE__
and __TIME__
can be used to insert the current date and time (at the time of preprocessing) into the code, respectively. You could for instance show this information in the version information for your program:
printf("%s v%s\nCompiled on %s at %s\n", PRG_NAME, PRG_VERSION, __DATE__, __TIME__);
Comments (413)