060 062


Common Sense C - Advice and Warnings for C and C++ Programmers:Macros and Miscellaneous Pitfalls






Common Sense C - Advice & Warnings for C and C++ Programmers

by Paul Conte

29th Street Press

ISBN: 1882419006   Pub Date: 10/01/92  




Previous Table of Contents Next Once is Enough When you create your own macros, you should try to avoid evaluating a macro argument more than once, if possible. This practice reduces the problem of unintended side effects. For example, an obvious improvement to the double macro definition is: #define double( x ) ( 2 * ( x ) ) Not all macros can be defined to avoid multiple references to their arguments (consider the problem with a max( x, y ) macro). If you want to avoid any chance of problems caused by multiple evaluation of arguments, use a function rather than a macro. Macros can contain almost any kind of source, including complete statements. When defining a macro, be sure to consider all the contexts in which the macro may be used. One difficult area is when a macro includes conditional logic. Suppose you have a macro to print messages only when a “trace” variable is on: #define ptrace( sts, str ) \ if ( sts ) printf( "%s\n", str ) A reference to ptrace might be: if ( x < 0 ) ptrace( traceon, "Negative input" ); else ptrace( traceon, "OK input" ); which, when expanded (and indented to show the logical structure) is: if ( x < 0 ) if ( traceon ) printf( "%s\n", "Negative input" ); else if ( traceon ) printf( "%s\n", "OK input" ); This code will not print a message when x is non-negative, regardless of the setting of traceon. This unintended result stems from the “dangling else” pitfall I described in Chapter 3. You can avoid the problem by always using braces for conditional statements, as I recommended. The following statements evaluate properly: if ( x < 0 ) { ptrace( traceon, "Negative input" ); } else { ptrace( traceon, "OK input" ); } But when you’re creating macros, you shouldn’t assume that the person using the macro will follow similar guidelines. Correcting this problem isn’t a simple matter of adding braces to the macro definition because you would then have to not place a semicolon after ptrace(…) when you used the macro — an unacceptable exception to normal C syntax. Instead, drawing on a suggestion by Andrew Koenig, you can restructure the macro as an expression instead of a statement: #define ptrace( sts, str ) \ ( (void) ( ( ! ( sts ) ) || printf( "%s\n", str ) ) ) The “trick” to this macro is the C standard that logical expressions are always evaluated using left-to-right, “short-circuit” evaluation. Thus, ( ! ( sts ) ) is evaluated first, and if sts is zero (false), the whole logical expression is true, and the second part (the printf) is never evaluated. If sts is non-zero (true), the printf is invoked as part of the expression evaluation. The (void) provides a generic type cast so ptrace can be used in expressions. When things get this complicated, however, it’s probably a good time to switch to a function or use C’s conditional compilation (#if…#endif) facilities. Although you can encounter some “gotcha’s” using macros, properly used they offer an essential means of insulating yourself from many of C’s other danger zones. Don’t hesitate to use macros, but don’t use them as a “lazy person’s” alternative to typedef’s, enumerations and functions, when one of these alternatives provides a better solution. Also, take care when you define macros not to set traps for the unwary programmer (who may be yourself) that uses your macros. The “Impossible” Dream Sometimes, it takes real character to program in C. For instance, suppose you compiled and ran the following code: unsigned char c; c = '\xff'; if ( c != '\xff' ) print( "Impossible!\n" ); would it seem impossible to print “Impossible!”? Not with some C compilers. The C standard lets compiler writers decide whether the default char type means signed char or unsigned char. The default sign of the char type affects how char values are converted in mixed-type expressions. If the default is signed, the compiler will convert the character constant '\xff' to a signed integer by extending the high-order bit. (Oddly enough, C defines character constants as int type.) Thus, '\xff' would have a 16-bit integer value of 0xffff. To evaluate c != '\xff', the compiler will convert the explicitly declared unsigned character c to the integer value 0x00ff, thus making it unequal to the value of the character constant '\xff'. It might seem this problem could be fixed by casting the character constant to an unsigned integer, as in if ( c != (unsigned) '\xff' ) but this cast simply converts 0xffff to an unsigned, rather than signed, int type. The immediate solution to this problem is to use the following cast: if ( c != (unsigned char) '\xff' ) The general rule is: Carefully cast any operation that involves a char variable and any operand other than another char variable. C attracts some odd “characters,” one of them being the manifest constant EOF, which is not really a character — it's an integer with a value of -1 — but which is returned by the getchar and other C functions. If you try the following loop with a compiler that uses unsigned char as the default for char variables: char c; while ( ( c = getchar() ) != EOF ) ... you’ll wait a long time before the loop ends. Because the value of c will always be treated as an unsigned integer, it will never equal -1. With a compiler that uses signed char as the default for char variables, the loop may end before the last character is read, since a character with a value that converts to an integer value of -1 may be read from the input stream. Why did the C library designers name a function “get character,” when the function actually returns an integer, and may cause your program to fail if you actually store the return value in a character variable? Maybe they were making a veiled suggestion that mastering this kind of C inconsistency was a good way for wimp programmers to “get some character.” In any case, don’t let something as “meaningless” as a function name trip you up. Always use int (not char) variables to store return values from fgetc, getc, getchar, putc, putchar, and ungetcfunctions. Previous Table of Contents Next Copyright © NEWS/400 Books

Wyszukiwarka

Podobne podstrony:
060 062
060 062 mxbaxaplnmsgni4ibvhjqci5ckqcre6qx7vsp2y
060 34
060 15
060 26 (2)
060 22
F F 060
01 Testy 343 [01] 0X 062 Arkusz Egzaminacyjny 0X 062 Etap Pisemny Czerwiec 2006id)60
060 00 (3)
060 18 (5)

więcej podobnych podstron