Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced C 1992

.pdf
Скачиваний:
93
Добавлен:
17.08.2013
Размер:
4.28 Mб
Скачать

Part IV • Documenting the Differences

Listing 15.1. continued

/*

*The DONOTHING identifier, although it is defined, is basically

*a no-operation. An example of its use is shown in the body

*of the program. Some programmers define DONOTHING as a

*semicolon; doing so, however, can create problems and is

*not recommended.

*/

#define DONOTHING

/*

*Both TRUE and FALSE, below, can be considered to be macros

*that don't have any operands. When they are included in a

*source line, they expand to their literal contents.

*/

 

#define TRUE

1

#define FALSE

(!TRUE)

/*

*Now define some stock macros. Both MIN() and MAX() may be

*included (in lowercase) in stdlib.h (with many C compilers);

*I define them in uppercase, however, to remind me that they

*are macros, subject to side effects.

*/

#define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b))

/*

*SWAP() is a neat little variable swapper that swaps the

*contents of two variables in-place without temporary storage.

*The only caution is that the variables must be of the same

*size (but can be of differing types if necessary).

*

*Notice SWAP()'s use of braces around the expressions, in

*case the macro is invoked as a single line after an if()

*statement that has no braces itself. Failure to include

*the braces can lead to some strange problems with macros

626

Preprocessor Directives

*that have more than one statement included in them, such

*as is the case with SWAP().

*/

#define SWAP(a, b) {a ^= b; b ^= a; a ^= b;}

/*

*Notice that PRINTAB() uses the stringize operator to form

*its format string. This usage enables you to have a nice

*printf() statement without having to do a lot of typing. */

#define PRINTAB(a, b) printf(#a" = %d "#b" = %d \n", a, b)

C C C

C15C C

C C C

int

main()

{

 

int

nOurTime = FALSE;

int

nSum;

int

a = 10;

int

b = 11;

/*

The DONOTHING identifier tells you that the for()'s statement(s)

* have not been omitted:

*/

 

 

for (nSum = 0; nSum == nOurTime; nSum++)

 

DONOTHING;

 

if (nOurTime == TRUE)

 

{/* Process our time... */

/*

our time code is here. */

 

}

PRINTAB(a, b);

continues

627

Part IV • Documenting the Differences

Listing 15.1. continued

SWAP(a, b);

PRINTAB(a, b);

return(FALSE);

}

Each of the macros in the MACROS program is useful in a real program. The MIN() and MAX() macros are the most useful to me. The SWAP() macro has been around a long time. (I came across it when I was writing assembly code for mainframe computers.) It enables two variables to be swapped without using any temporary storage—a handy tool if you are either short of memory or cannot allocate a temporary variable because of context.

The #error Directive

The #error directive typically is used as part of an #if type conditional preprocessor statement. For example, if you include in MACROS.C the #error directive as shown in the final line of this code fragment:

/* Program MACROS, written 23 June 1999 by Peter D. Hipson */

#include <stdio.h> #include <stdlib.h>

#error "This is an error"

This directive, when encountered by the compiler, prints on the compiler’s stderr stream a message similar to the following:

D:\ADC\SOURCE\MACROS.C(6) : error C2189: #error : "This is an error"

Notice that the compiler’s error message includes the source file’s name and line number, and a compiler error message that includes the string included in the #error directive.

628

Preprocessor Directives

C C C

 

15C

 

C C C

 

C C

The #include Directive

The #include directive tells the compiler to include a header file at the current point in the source file. Header files generally don’t contain actual executable statements, but rather contain #define identifiers, function prototypes, and so on.

When you are including a header file, delimit the operand using either double quotes or a <> pair surrounding the operand.

The directive #include "ourfile.h" causes the compiler to search for the header file first in the current directory and then in the directories specified in the include search order.

The directive #include <ourfile.h> causes the compiler to search for the header file in the directories specified in the include search order.

If the header file specified in the #include directive has a fully qualified path name, this path is used to find the header file.

The #if Directive

The #if directive is a useful part of the preprocessor’s directives. It enables you to conditionally include (and exclude) parts of your source code. Unlike the other preprocessor directives usually grouped at the beginning of a source file, the #if directive may be found at any point in the source file.

The #if directive is followed by a constant expression (which the preprocessor evaluates) and has several restrictions, including the following:

The expression must evaluate to constants. A variable cannot be used in the #if directive.

The expression must not use sizeof(), a cast, or enum constants.

The expression is evaluated mathematically; string compares do not work.

The #if directive enables you to compile code conditionally based on factors such as which operating system your program will run under and the compiler’s memory model. When the #if directive is used, you can combine the #if directive with either the #else or the #elif directives.

629

Part IV • Documenting the Differences

The block of source code affected by the #if directive is ended with an #else, #elif, or #endif directive.

The #ifdef Directive

To test whether an identifier has been defined, you can use the #ifdef directive. The identifier to be tested can be one you create (using #define) or a predefined identifier created by the C compiler.

You might use the #ifdef directive to avoid creating problems with macro redefinitions or for specific macros being redefined with slightly different (but functionally identical) operands.

The most common use I have for the #if directive is to comment out large blocks of code that may contain embedded comments. Because you cannot use a surrounding comment with such a block of code, a simple workaround procedure is to use #if as shown in this example:

for (nSum = 0; nSum == nOurTime; nSum++) DONOTHING;

#ifdef DONOTCOMPILE /* Never compile this code */

 

if (nOurTime

==

TRUE)

 

{/* Process our

time... */

/*

our time

code is here. */

 

}

 

 

#endif /* DONOTCOMPILE */

PRINTAB(a, b);

Notice that the if() block of code (with its comments) has been removed using an #ifdef/#endif directive pair. I make sure that the identifier DONOTCOMPILE never is defined. You can choose to use a different identifier; DONOTCOMPILE, however, is descriptive.

A second use of the #ifdef directive is to enable you to undefine an identifier if you know that you will redefine it.

630

Preprocessor Directives

C C C

 

15C

 

C C C

 

C C

#ifdef SWAP #undef SWAP #endif

#define SWAP(a, b) {a ^= b; b ^= a; a ^= b;}

In this code fragment, you want to define a macro called SWAP(); however, you want to avoid any problems in redefining an existing SWAP() macro. This code is necessary to avoid an error message if SWAP() already exists and then is redefined.

The #ifndef Directive

To test whether an identifier has not been defined, you can use the #ifndef directive. The identifier to be tested can be one you create (using #define) or a predefined identifier created by the C compiler. If the identifier has not been defined, the statements following the #ifndef directive are executed until either a #endif, #elif, or #else is encountered.

An example of the #ifndef directive is the code used to prevent a header file that is included twice from creating problems with macro redefinitions. An example of the use of #ifndef to prevent problems with multiple inclusion of a header file is shown in the following code fragment. This code fragment assumes that the header file’s name is OURHEAD.H.

#ifndef _ _OURHEAD

/* The include file's lines are here... */

#define _ _OURHEAD #endif /* _ _OURHEAD */

Notice that the first time the header file is included, the identifier __OURHEAD will not have been defined, and the test will succeed. The lines for the header file are processed and the final line defines the identifier __OURHEAD. If this header file is included a second time, the identifier __OURHEAD then is defined, and the test fails. The lines for the header file are ignored.

A second use of the #ifndef directive is to enable you to define an identifier if it has not been defined yet. In the following classic example, the NULL identifier (which is in a number of header files) is defined.

631

Part IV • Documenting the Differences

#ifndef NULL

#define NULL (int *)0 #endif

In this code fragment, be sure that the NULL identifier is defined. If it is defined, you accept the definition; if it is not defined, you define it. This code is necessary to avoid an error message if NULL already exists and then is redefined.

The #else Directive

Like the C language’s if() statement, the preprocessor has an if/else construct. Often, you must use either one block of code or another depending on the results of a given test. You can make two separate #if tests, each having the opposite effect; this technique, however, creates code that is generally unreadable and difficult to maintain. (You might change one #if and forget to update the other.)

Typically, #else is used when you need to use one block of source code (which can include other preprocessor directives), or another, but never both. An example of the #else is shown in the following code fragment:

#if defined(VERSION1)

/* Lines for the first version. */ #else /* it's not VERSION1 */

/* Lines for the other versions. */ #endif /* VERSION1 testing */

The same code written without the #else is more complex and more difficult to understand:

#if defined(VERSION1)

/* Lines for the first version. */ #endif /* Not VERSION1 */

#if !defined(VERSION1)

/* Lines for the other versions. */ #endif /* Not VERSION1 */

Using #else when it’s needed can make your preprocessor code more manageable.

632

Preprocessor Directives

C C C

 

15C

 

C C C

 

C C

The #elif Directive

To make the creation of nested testing using preprocessor directives easier, ANSI C has introduced the #elif directive. It follows an #if (or another #elif) directive, effectively ending the #if (or #elif)’s block and introducing a new conditional block.

Typically, #elif is used where you need multiple tests, for example, to test for two or more versions of a compiler. An example of the #elif is shown in the following code fragment:

#if defined(VERSION1)

/* Lines for the first version. */ #elif defined(VERSION2)

/* Lines for the second version. */ #elif defined(VERSION3)

/* Lines for the third version. */ #endif /* VERSION? */

The same code written without the #elif is more complex and more difficult to understand:

#if defined(VERSION1)

/* Lines for the first version. */ #else /* Not VERSION1 */

#if defined(VERSION2)

/* Lines for the second version. */ #else /* Not VERSION2 */

#if defined(VERSION3)

/* Lines for the third version. */ #endif /* VERSION3 */

#endif /* VERSION2 */ #endif /* VERSION1 */

Using #elif when it’s needed can make your preprocessor code more manageable.

The #endif Directive

The #endif directive ends the nearest #if preceding it. Every #if requires an #endif statement; #else and #elif directives, however, do not require separate #endif statements.

633

Part IV • Documenting the Differences

The following code fragment shows nesting of conditional preprocessor statements and the effect of the #endif statements. Notice how the comments following the #endif directives make the code easier to read and modify. Try to get into the practice of commenting your preprocessor directives just as you would comment your regular C source code.

#ifdef DLL /* Creating a Dynamic Link Library */ #ifndef MT /* DLLs must be multitasking */

/* Error message for this condition! */ #error "Cannot define DLL without MT" #else

/* whatever is required for a DLL */

#endif

/*

MT

*/

#endif

/*

DLL

*/

Using nested conditional preprocessor directives enables you to make complex decisions about what is being done with the program. Your program (commonly) might have three versions: a low-priced entry version, a higher-priced full-featured version, and a freebie demo version. Using #if directives enables you to maintain one set of source code that compiles differently depending on which version of the program is being created.

The #line Directive

By using the #line directive, you can change either the current line number (which then is successively incremented for following lines) or, optionally, the filename associated with the current source file.

When you are using an integrated debugger, which checks the error and warning messages produced by the compiler, changing the line or filename can produce results that may not be what you expect. When you are using #line, make sure that you understand what its effects will be in your environment.

The current line number is always available in the predefined identifier _ _LINE __, and the current filename is available in the predefined identifier __FILE __. Both of these identifiers are used with the assert() macro, and you can create error message substitutions using them. Let’s look at an example of using the #line directive.

634

Preprocessor Directives

/* Source file is OURFILE.C */

printf("This file is '%s' the line is '%d' \n", _ _FILE _ _, _ _LINE _ _);

#line 10000 "DEBUGIT.C"

C C C

C15C C

C C C

printf("This file is '%s' the line is '%d' \n", _ _FILE _ _, _ _LINE _ _);

Here are the results of running this program fragment:

This file is 'OURFILE.C' the line is '3'

This file is 'DEBUGIT.C' the line is '10002'

The #line directive can be useful when your source files don’t have meaningful names and renaming them is not practical.

The #pragma Directive

The #pragma directive is, by ANSI standards, implementation-defined. It is used to issue special commands to the compiler, using a standardized method. Do not assume that any specific operand with #pragma is present when using a given compiler. Two pragmas are relatively common—message and pack. Your compiler probably offers several other pragmas in addition to these two.

The message Pragma

The message pragma enables you to write a message to stderr while the file is compiling. When you are debugging, check to see which identifiers are defined. The following code fragment has a message indicating which of the three versions of the program is being compiled:

#if defined(VERSION1)

#pragma message "The first version." #elif defined(VERSION2)

#pragma message "The second version." #elif defined(VERSION3)

#pragma message "The third version." #endif /* VERSION? */

635