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 youre creating macros, you shouldnt assume that the person using the macro will follow similar guidelines. Correcting this problem isnt 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, its probably a good time to switch to a function or use Cs conditional compilation (#if
#endif) facilities.
Although you can encounter some gotchas using macros, properly used they offer an essential means of insulating yourself from many of Cs other danger zones. Dont hesitate to use macros, but dont use them as a lazy persons alternative to typedefs, 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 ) ...
youll 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, dont 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 062060 062 mxbaxaplnmsgni4ibvhjqci5ckqcre6qx7vsp2y060 34060 15060 26 (2)060 22F F 06001 Testy 343 [01] 0X 062 Arkusz Egzaminacyjny 0X 062 Etap Pisemny Czerwiec 2006id)60060 00 (3)060 18 (5)więcej podobnych podstron