The Psychology of Quality and More |
CHAPTER 9 : Data Usage
9.10 Using ConstantsConstants are used to define limits, special values and to simulate types. Used carelessly they can be a major source of confusion, but with careful use they will make code much more understandable. 9.10.1 Magic NumbersWherever a bare number is used in code, the exact meaning of the number is seldom clear:
FileStatus = 5; /* what does this mean?? */
Numbers like this are called magic numbers, as their purpose is usually quite a mystery. The solution is to always #define magic numbers:
#define F_CORRUPT 5
In use, it is clearer if just the #define'd constant is used, rather than attaching a numeric correction, such as MAX_WORD_COUNT-1, which may be missed or misunderstood. Ranges and limited valuesConstants are commonly used to describe the valid values for variables. Range variables may have any value between an upper and lower bound:
#define MAX_LINE_LEN 80
It may be useful to define limits for all variables, including basic int's, etc. (this may be already defined in limits.h):
#define INT_MAX 0x7FFF /* maximum positive 16-bit
int */ ---------------------------------------------------------------------- Limited-value variables can only have one of a defined set of values (note: enum may be better for this):
int ScreenColor; Over-useIt is possible to over-use #define's, for example where a constant may be logically made up of an expression containing other constants:
#define SCRN_WIDTH 80 /* characters across screen */
In this case, it would be clearer if SCRN_AREA was #define'd as (SCRN_WIDTH * SCRN_HEIGHT) (also, a change in screen height would make SCRN_AREA wrong!). It may even be better to use this expression directly in the code:
char ScrnBuffer[SCRN_WIDTH * SCRN_HEIGHT];
It is also going too far to #define simple values, such as 0 and 1, where they are initializations for counts:
#define ZERO 0 /* this is overdoing it! */
A problem with #define'd constants is that they may be as unclear to the reader as the original magic number. This is particularly true for cases where a constant appears only once in the code. The reader may have to divert to finding the #define to read the comment before continuing to read the code. This situation may be helped with commenting. 9.10.2 Make constants clearLong constants are better suffixed by uppercase 'L' than by lowercase 'l', as 'l' may be mistaken for the number '1'. Thus use '121212L', rather than '121212l'.
Where a constant is to be used for its bit-pattern, then octal or (more commonly these days) hexadecimal should be used. It is clearer with hexadecimal to use lowercase 'x', as the 'dip' in character height makes it stand out more than uppercase 'X'. Actual hex digits are then made uppercase to maintain this differential. It is also being explicit to show leading zeros, to demonstrate the full intended word size:
#define STATUS_BITS Ox00F0 /* not 0XF0 or 240 */
Note that when high-end bits are required to be set it is more portable to use one's complement. Thus '~0x0001' will have all except the least significant bit set, even in a 32 bit int. Printing character constants should be given as quoted character, rather than decimal or hexadecimal:
FirstChar = 'A'; /* not 0x41 or 65 */
Non-printing characters can be shown in the same manner as hexadecimal numbers above, remembering that these should be restricted to single characters only:
#define BELL '\0x07' /* ASCII bell character */
9.10.3 TRUE and FALSEA boolean value in C is false if it is zero and true if it is non-zero. It is common practice to define:
#define FALSE 0
Generally, simple expressions using clear boolean names do not need to be compared directly with TRUE or FALSE:
if ( DoorIsOpen )
More complex expressions or functions may be clearer with a comparison against TRUE or FALSE:
if ( (DoorIsOpen = CheckDoorOpen(Door)) == TRUE )
But what if CheckDoorOpen() returned values other than 1 or 0? These would be interpreted as FALSE! This could be handled by comparing for non-equality with FALSE. However, the real problem is one of type: we are comparing a non-boolean expression against boolean values. It is simpler and more explicit to use TRUE and FALSE only in guaranteed boolean situations. This can be emphasized by tying together definition of boolean type and its permissible values:
typedef enum { FALSE = 0, TRUE } BOOL;
Functions and expression which can return any other values should return and be compared with constants #define'd or enum'd specifically for them. Where a generic fail code is used (typically zero) then #define a generic constant, rather than use the 'convenient' FALSE:
#define FAIL 0
9.10.4 Using 'sizeof'Wherever the size of a data item is used, then it is easier and more portable to use sizeof than a #define'd number. Thus:
alloc( sizeof(Buffer) ) ..not.. alloc( BUFFER_SIZE )
This is particularly true when dealing with structures, where the actual amount of memory allocated may be greater than the sum of the sizeof's of the elements:
struct
Although sizeof is useful, there are some situations where it should not be used, such as where it either makes no sense (e.g. trying to find the size of a function or a void) or where it may be misleading (eg. trying to find the size of an expression which contains side effects). 9.10.5 Using 'enum'Enumerations types may not be fully portable, but many compilers do support them, and they enable a more explicit definition of types, which the compiler can help check for correct usage. Using enum tagsEnums may be declared with just a tag the type can be declared, and the tag subsequently used to simplify and standardize declarations (just like using struct tags):
enum FLOWER { ROSE, PETUNIA, RHODADENDRON };
The sequential nature of the enum declaration allow uses such as:
for ( Flower = ROSE; Flower <= RHODADENDRON; Flower++ )
This helps the readability of the code but also opens the possibility of error and should thus be used with care. Error valuesIt is common to use zero as an error value. Thus when defining status values, it is a good idea to always make the first value the error status (possibly explicitly stating it as zero):
enum FD_RTNS { FD_ERR = 0, FD_OPEN, FD_CLOSED };
This method can help catch uninitialized variables (typically using assert()), whether or not zero is a valid value.
|
Site Menu |
Quality: | Quality Toolbook | Tools of the Trade | Improvement Encyclopedia | Quality Articles | Being Creative | Being Persuasive | |
And: | C Style (Book) | Stories | Articles | Bookstore | My Photos | About | Contact | |
Settings: | Computer layout | Mobile layout | Small font | Medium font | Large font | Translate | |
You can buy books here |
And the big |