How we change what others think, feel, believe and do

# CHAPTER 9 : Data Usage

## 9.10  Using Constants

Constants 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 Numbers

Wherever 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
...
FileStatus = F_CORRUPT;

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 values

Constants 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
#define MIN_LINE_LEN    0

int     LineLen;

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 */
#define INT_MIN 0x8000              /* minimum negative 16-bit int */
#define UINT_MAX 0xFFFF             /* maximum unsigned int        */
...

----------------------------------------------------------------------

Limited-value variables can only have one of a defined set of values (note: enum may be better for this):

int     ScreenColor;
/*
* values for ScreenColor
*/
#define BLACK   0
#define WHITE   1

#### Over-use

It 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   */
#define SCRN_HEIGHT 25      /* lines on screen            */
#define SCRN_AREA   2000    /* characters on 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! */
...
for ( WordCount = ZERO; WordCount < MAX_WORD_COUNT; WordCount++ )

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 clear

Long 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 */
#define CRLF    '\0x0D0A'       /* WRONG: Non-portable  */

### 9.10.3  TRUE and FALSE

A 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
#define TRUE    1

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
...
if ( (DoorIsOpen = CheckDoorOpen(Door)) != FAIL )

### 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
{
char    Initial;    /* sizeof(Initial) = 1       */
int     EmpNo;      /* sizeof(EmpNo) = 2         */
} EmpID;                /* BUT: sizeof(EmpID) = 4    */

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 tags

Enums 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 };
struct
{
enum FLOWER  FrontBed;
enum FLOWER  RearBed;
} Garden;
...
Garden.FrontBed = ROSE;

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 values

It 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 };

enum RM_VALUES { RM_FIRST = 1, RM_SECOND, RM_THIRD };   /* zero is error! */

This method can help catch uninitialized variables (typically using assert()), whether or not zero is a valid value.

And the big
paperback book