10.3.3 More Complex Macros
Normally a macro definition extends to the end of the line beginning with the command #define. However, long macros can be split over several lines by placing a \ at the end of the line to be continued. For example,#define ERROR(condition, message) \
if (condition) printf(message)
A more interesting example, adapted from [KP99, page 240], performs a timing loop on a section of code using the standard library function clock(), which returns processor time in milliseconds.3
#define TIMELOOP(CODE) { \
t0 = clock(); \
for (i = 0; i<n; ++i) { CODE; } \
printf("%7d ", clock() - t0); \
}
This macro might be used as follows.
TIMELOOP(y = sin(x));
It is possible to convert a token to a string constant by writing a macro that uses the # symbol in the manner of the following example.
#define PRINT_DEBUG(expr) printf(#expr " = %g\n", expr)
3 Measuring the runtime of code is a tricky business as the function in question might have a short execution time compared to the latency of the timing function itself. Thus, reasonable measurements require running the function multiple times and timing the period of the entire loop.
This macro when invoked will print the expression and its result, as the first instance of the expression is converted to a string constant by the preceding #. For example,
PRINT_DEBUG(x/y);
is expanded to
printf("x/y" " = %g\n", x/y);
Another rather obscure preprocessor operator is ##, which provides a way to concatenate two tokens. For example,
#define TEMP(i) temp ## i
might be used to create di erent temporary variable names
TEMP(1) = TEMP(2);
which, after preprocessing, becomes
temp1 = temp2;
The preprocessor defines a number of predefined macros. These are __LINE__, __FILE__, __DATE__, __TIME__, __STDC__, and __STDC_VERSION__. Notice that each of these names is prefixed and su xed by double underscore characters. Determining the meaning of each of these macros is left as an exercise to the reader.
To put the various aspects of this section together, consider the following macro,
#define PRINT_DEBUG(expr, type) \
printf("File: " __FILE__ \
"\nLine: %d\nExpr: " #expr \
" = %" type##TYPE "\n", __LINE__, (expr))
which, given a set of definitions for formatting di erent types, e.g.,
#define intTYPE "d"
#define doubleTYPE "f"
can be used as
PRINT_DEBUG(x/y, double);
which will print the source-file name, the statement line number, the expression and its value. Indeed a clever and useful debugging macro.
10.4 Conditional Compilation
The C preprocessor provides a series of directives for conditional compilation: #if, #elif, #else, #ifdef, #ifndef, and #endif. These commands cause the preprocessor to include or exclude sec-tions of the source code from compilation depending on certain conditions. Conditional compilation is used for three main purposes: to optionally include debug code, to enclose non-portable code, and to guard against multiple inclusion of header files.It is common to write short sections of code exclusively for debugging purposes; this is often called “instrumentation”. This code should be present in the program during a debug build but removed for the final release build. However, it is a good idea to not actually delete the code, but to optionally include it using a preprocessor condition so that it is still available if further debugging is required. Typically, during debug builds, one defines a symbolic constant DEBUG so that debug code is included. For example,
#ifdef DEBUG
printf("Pointer %#x points to value %f", pd, *pd); #endif
When writing a program that contains non-portable code, it is good practice to isolate the non-portable parts in a separate source file. The di erent code sections for di erent machines are then enclosed in preprocessor conditions so that only the code for the specified machine is compiled. For example,
#ifdef __WIN32__ /* Code specific to Windows. */
return WaitForSingleObject(handle, 0) == WAIT_OBJECT_0;
#elif defined(__QNX__) || defined(__linux__) /* Code specific to QNX or Linux. */ if(flock(fd, LOCK_EX | LOCK_NB) == -1) return 0;
else return 1;
#endif
A header file should only be included once in any given source file (although they may appear in any number of di erent source files in the program). Otherwise certain symbols might obtain multiple definitions, which would result in a compilation error. This problem can occur if some header files include other header files, such that several headers are dependent on a common header file. To prevent the problem of multiple inclusion, the following preprocessor idiom is applied. Consider a header file aheader.h, which begins and ends with the preprocessor commands below.
#ifndef A_HEADER_H_
#define A_HEADER_H_
/* Contents of header file is contained here. */
#endif
By prefixing the file with #ifndef A_HEADER_H_, the header is included the first time (presuming A_HEADER_H_ is not defined previously), but A_HEADER_H_ is subsequently defined in the next line. This prevents any subsequent inclusion of the header for a given source-file. This idiom is known as a “header guard”.
No comments:
Post a Comment