I l @ ve RuBoard |
The #undef directive cancels an earlier #define definition. The #if , #ifdef , # ifndef , #else , #elif , and #endif directives are typically used to produce files that can be compiled in more than one way.
The #undef directive undefines a given #define . That is, suppose you have this definition:
#define LIMIT 400
Then the directive
#undef LIMIT
removes that definition. Now, if you like, you can redefine LIMIT so that it has a new value. Even if LIMIT is not defined in the first place, it is still valid to undefine it. If you want to use a particular name and you are unsure whether it has been used previously, you can undefine it to be on the safe side.
With some compilers, you can redefine a defined name and nest #define and #undef directives so that undefining a name causes it to revert to its original value. This is not, however, the standard practice, and it is not part of the ANSI standard. More typically, you'll get a warning or error message if you redefine a name, so use #undef first if you want to redefine. Remember, however, that you can nest const constants as long as each new definition is in its own subblock, as Listing 16.5 shows.
/* nestcnst.c -- use const for nested constants */ #include <stdio.h> const int BIG = 20; void big(void); int main(void) { const int BIG = 3; /* hides global BIG */ { const int BIG = 100; /* hides the BIG that equals 3 */ printf("%d\n", BIG); /* displays 100 */ } printf("%d\n", BIG); /* displays 3 */ big(); return 0; } void big(void) { printf("%d\n", BIG); /* displays 20 */ }
You can use the other directives mentioned to set up conditional compilations. That is, you can use them to tell the compiler to accept or ignore blocks of information or code according to conditions at the time of compilation.
A short example will clarify what conditional compilation does. Consider the following:
#ifdef MAVIS #include "horse.h" /* gets done if MAVIS is #defined */ #define STABLES 5 #else #include "cow.h" /* gets done if MAVIS isn't #defined */ #define STABLES 15 #endif
Here we've used the indentation allowed by newer implementations and by the ANSI standard. If you have an older implementation, you might have to move all the directives, or at least the # symbols (see the next example), to flush left:
#ifdef MAVIS # include "horse.h" /* gets done if MAVIS is #defined */ # define STABLES 5 #else # include "cow.h" /* gets done if MAVIS isn't #defined */ # define STABLES 15 #endif
The #ifdef directive says that if the following identifier ( MAVIS ) has been defined by the preprocessor, then follow all the directives and compile all the C code up to the next #else or #endif , whichever comes first. If there is an #else , then everything from the #else to the #endif is done if the identifier isn't defined.
Incidentally, an "empty" definition like
#define MAVIS
is sufficient to define MAVIS for the purposes of #ifdef .
The form #ifdef #else is much like that of the C if else . The main difference is that the preprocessor doesn't recognize the braces ( {} ) method of marking a block, so it uses the #else (if any) and the #endif (which must be present) to mark blocks of directives. These conditional structures can be nested. You can use these directives to mark blocks of C statements, too, as Listing 16.6 illustrates.
/* ifdef.c -- uses conditional compilation */ #include <stdio.h> #define JUST_CHECKING #define LIMIT 4 int main(void) { int i; int total = 0; for (i = 1; i <= LIMIT; i++) { total += 2*i*i + 1; #ifdef JUST_CHECKING printf("i=%d, running total = %d\n", i, total); #endif } printf("Grand total = %d\n", total); return 0; }
Compiling and running the program as shown produces this output:
i=1, running total = 3 i=2, running total = 12 i=3, running total = 31 i=4, running total = 64 Grand total = 64
If you omit the JUST_CHECKING definition (or enclose it inside a C comment) and recompile the program, only the final line is displayed. You can use this approach, for instance, to help in program debugging. Define JUST_CHECKING and use a judicious selection of #ifdefs , and the compiler will include program code for printing intermediate values for debugging. After everything is working, you can remove the definition and recompile. If, later, you find that you need the information again, you can reinsert the definition and avoid having to retype all the extra print statements. Another possibility is using #ifdef to select among alternative chunks of codes suited for different C implementations.
The #ifndef directive can be used with #else and #endif in the same way that #ifdef is. The #ifndef asks if the following identifier is not defined; #ifndef is the negative of #ifdef . This directive is often used to define a constant if it is not already defined.
#ifndef SIZE #define SIZE 100 #endif
Again, older implementations might not permit indenting the #define directive. Suppose this directive were in an include file called arrays.h . Placing the line
#include "arrays.h"
at the head of a file would result in SIZE being defined as 100, but placing
#define SIZE 10 #include "arrays.h"
at the head would set SIZE to 10. Here, SIZE is defined by the time the lines in arrays.h are processed , so the #define SIZE 100 line is skipped . You might do this, for example, to test a program using a smaller array size. When it works to your satisfaction, you can remove the #define SIZE 10 statement and recompile. That way, you never have to worry about modifying the header array itself.
The #ifndef directive is commonly used to prevent multiple inclusions of a file. That is, a header file can be set up along the following lines:
/* things.h */ #ifndef _THINGS__H_ #define _THINGS_H_ /* rest of include file */ #endif
Suppose this file somehow got included several times. The first time the preprocessor encounters this include file, _THINGS_H_ is undefined, so the program proceeds to define _THINGS_H_ and to process the rest of the file. The next time the preprocessor encounters this file, _THINGS_H_ is defined, so the preprocessor skips the rest of the file.
Why would you include a file more than once? The most common reason is that many include files include other files, so you may include a file explicitly that another include file has already included. Why is this a problem? Some items that appear in include files, such as declarations of structure types, can appear only once in a file. The standard C header files use the #ifndef technique to avoid multiple inclusions. One problem is to make sure the identifier you are testing hasn't been defined elsewhere. The usual solution is to use the filename as the identifier, using uppercase, replacing periods with an underscore, and using an underscore (or, perhaps, two underscores) as a prefix and a suffix. Listing 16.7 shows an example
/* names.h -- define names structure */ #ifndef _NAMES_H_ #define _NAMES_H_ #define SLEN 32 struct names { char first[SLEN]; char last[SLEN]; }; #endif
You can test this header file with the program shown in Listing 16.8 . This program should work correctly when using the header file shown in Listing 16.7 , and it should fail to compile if you remove the #ifndef protection from Listing 16.7 .
/* doubincl.c -- include header twice */ #include <stdio.h> #include "names.h" #include "names.h" /* accidental second inclusion */ int main() { struct names winner = {"Less", "Ismoor"}; printf("The winner is %s %s.\n", winner.first, winner.last); return 0; }
The #if directive is more like the regular C if . It is followed by a constant integer expression that is considered true if non-zero , and you can use C's relational and logical operators with it.
#if SYS == 1 #include "ibm.h" #endif
You can use the #elif (not available in some older implementations) directive to extend an if-else sequence. For example, you could do this:
#if SYS == 1 #include "ibmpc.h" #elif SYS == 2 #include "vax.h" #elif SYS == 3 #include "mac.h" #else #include "general.h" #endif
Many newer implementations offer a second way to test whether a name is defined. Instead of using
#ifdef VAX
you can use this form:
#if defined (VAX)
Here, defined is a preprocessor operator that returns 1 if its argument is #defined and otherwise . The advantage of this newer form is that it can be used with #elif . Using it, you can rewrite the previous example this way:
#if defined (IBMPC) #include "ibmpc.h" #elif defined (VAX) #include "vax.h" #elif defined (MAC) #include "mac.h" #else #include "general.h" #endif
If you were using these lines on, say, a VAX, you would have defined VAX somewhere earlier in the file with this line:
#define VAX
One use for these conditional compilation features is to make a program more portable. By changing a few key definitions at the beginning of a file, you can set up different values and include different files for different systems.
The ANSI C standard stipulates that implementations conforming to the standard predefine the name __STDC__ . Therefore, you can use the test
#if defined (__STDC__)
to check whether the compiler is ANSI C compliant.
I l @ ve RuBoard |