Previous: 3.3.1 The C Preprocessor
Up: 3.3 Coding Programs for Readability
Next: 3.3.3 Including Header Files
Previous Page: 3.3.1 The C Preprocessor

3.3.2 Macros

In Chapter we introduced the define compiler directive which defines symbolic names for strings of characters. Such a string of characters can be arbitrary, for example a sequence of characters representing a numeric constant. These names can then be used anywhere in the program instead of the string itself. The C preprocessor replaces these symbolic names with the specified strings prior to compiling the program. We have seen examples where using names for arbitrary strings makes it easy to change all occurrences of these names by merely changing the definitions. It also makes for easier reading and debugging of programs by allowing the programmer to use a name which has some meaning rather than some ``magic number''.

The definition is called a macro and the preprocessor performs a macro expansion when it substitutes the string for the name. A macro definition takes the form:

The macro names follow the same rules as identifiers, however, a common convention observed by most C programmers is to name macros in all upper case to distinguish them from program variables. No quotation marks are used to delimit the string, nor is the directive terminated by a semi-colon. Instead, the string extends to the end of the line (an escape character, , can be used to continue the string on the next line). For example, the following are macro definitions:
#define     PI          3.14159
                #define     SIZE        1000
                #define     RSQUARED    radius * radius
                #define     AREA        PI * RSQUARED
                #define     LONG        This is a very long macro  
                definition we continued to the next line
When directives such as these appear in the source file, then the macros are said to have been defined. We have defined macros for the symbols PI, SIZE, RSQUARED, AREA and LONG. With the above definitions, the defined names may be used anywhere in program statements. The preprocesser generates the expanded source code by string replacement, for example:
Original code         Expanded code after preprocessing

circum = 2 * PI * radius; circum = 2 * 3.14159 * radius; y = x + SIZE; y = x + 1000; printf("SIZE = ",SIZE); printf("SIZE = ",1000); AREA; 3.14159 * radius * radius;

As can be seen, the preprocessor replaces the macro name with the specified replacement string in the entire source file following the definition. The substitution is not made if a macro name, occurs in double quotes as in the format string in the printf() statement shown above.

The scope of the macro definition is the entire source file following the definition line. The definitions may be removed at any point in the program by a directive #undef, for example:

#undef SIZE
The above directive makes the preprocessor ``forget'' the previous definition for SIZE. If desired, a new definition may be specified for SIZE at this point. It is a common practice to put macro definitions at the top of the source file, unless the old definitions are removed at some point in the source file and new definitions are specified:
#define SIZE 40          /* SIZE is define to be the string 40 */
     ...
     #undef SIZE              /* SIZE is undefine */
     #define SIZE 100         /* SIZE is defined to be 100 */
Identical definitions for identifiers may appear in a file without causing any problems; however, two different definitions for an identifier represent an error.
#define SIZE 40
     #define SIZE 40     /* OK */
     #define SIZE 100    /* ERROR */
The only way to make a new definition for an identifier is to first undefine it, i.e. remove its first definition.

Macros with Arguments

Macro definitions may also have formal parameters which are replaced by the actual arguments given in the macro call. This is similar to parameters in function calls; however, macro arguments are treated as strings of characters and are substituted for parameters by the preprocessor; no evaluation takes place. Consider the example:

#define READ_FLT(fvar)   scanf("%f", &fvar)
The macro encapsulates the expression for reading a float number, i.e. a macro call is replaced by a string that represents a correct scanf() function call to read a float number into an object passed to the macro. The actual argument in a macro call replaces fvar in the replacement string. In other words, every time the macro is called, the expanded code is substituted literally except that fvar in the definition is replaced by the argument given in the actual call. Here are some examples of macro calls with parameters together with the expanded code:
macro call          Expanded Code

READ_FLT(x); scanf("%f", &x); READ_FLT(rate); scanf("%f", &rate);

Macro calls in these cases expand to C statements. Such calls are said to expand to in-line code, because the resulting code represents statements in the source code. These types of macro calls can be used in place of function calls, for example, instead of writing a function to square a number, we can define a macro:
#define SQ(x)       (x * x)
We can use such a macro in any expression, e.g.,
y = SQ(radius);
     printf("Square of %d is %d\n", radius, SQ(radius));
However, remember, macro calls are substitutions, and macro parameters are neither evaluated nor checked for data type consistency. Therefore, proper placement of parentheses is important in macro definitions. For example, consider the following macro call and expanded code:
SQ(x+y)
expanded becomes
(x + y * x + y)
The expanded code is not the square of (x + y), as we would expect. By precedence rules, it is a sum of three terms, x, y * x, and y. A proper definition of a macro for square should be:
#define SQ(x)  ((x) * (x))
With this definition,
SQ(x+y)
will expand correctly to
((x + y) * (x + y))
Here is a simple example program:
/* File: macro.c */
     #define READ_FLT(fvar)   scanf("%f", &fvar)
     #define PI     3.14159
     #define SQ(x)  ((x) * (x))

main() { float radius;

printf("Type Radius: "); READ_FLT(radius); printf("Area of a circle with radius %6.2f is %6.2f\n", radius, PI * SQ(radius)); }

The output of a sample run is:

Why use macros with arguments when functions will serve the same purpose? The advantage is practical, NOT logical. When a function is called, there is a certain amount of run time overhead, i.e. extra time needed during execution. The overhead comes from passing arguments, transferring control, returning a value, and returning control. If a function is called just a few times, the overhead is negligible. However, if a function is used numerous times, e.g. in a loop executed many times, then the overhead can become significant.

A macro on the other hand has no run time overhead. It is expanded at compile time into in-line code which has no overhead at run time. If execution time for a program is a problem because of a frequently used routine, then writing a macro for that routine makes good sense, as long as the operation can be simply expressed as a macro.

An Example Program

Let us look at another example program to make use of these new facilities.

Task

Read a set of high temperature readings for some number of days and to count the number of nice days, bad days, and the average temperature for the period. Nice days are those days whose temperature falls within some ``comfort zone''.

The high level algorithm for this task is straight forward;

prompt the user and read first temperature
      while there are more days to read
            process one day's temperature
            accumulate total temperature
            read the next temperature
      print results
With this algorithm, we next consider what information we will be working with in this program. We read daily temperatures, so we will need a variable for that, and variables to count the number of nice and bad days. Since we compute the average temperature, we accumulate the total of all the daily temperatures, so we need a variable for that. Next we consider how we will implement the algorithm using functions to hide details. For example, the step to print results, printing the number of nice and bad days as well as computing and printing the average temperature can be done in a function, print_results(), which is given the number of nice days, bad days, and the cumulative total of temperatures. The step of processing one day's temperature is another candidate; however, this step involves updating our counts of nice and bad days. Since, as we have seen, functions cannot access variables local to main, we refine our algorithm to fill in some of the details of this step:
prompt the user and read first temperature
      while there are more days to read
            if it's a nice day, count a nice day
            otherwise count a bad day
            accumulate total temperature
            read the next temperature
      print results
We can use a function to test if a day is nice, thus hiding the details of this operation. We are now ready to write the code for main() as shown in Figure 3.9.

It should be noted we have made an additional design decision here; we use a zero value for the temperature read in as the loop termination. Also not that we have provided prototype statements for our functions, nice_day() and print_results(). This is sufficient information about these functions when considering the logic of main(). (We have specified the return value of print_results as type int, but the function has no real meaningful return value).

We next turn out attention to the function, nice_day(). This function is given the temperature and should return True if this qualifies as a nice day, and False otherwise. The task specified that the temperature of a nice day is to fall within some ``comfort zone'', i.e. not too cold and not too hot. We can write the algorithm for this function from this information:

if temperature is too cold, return False
      if temperature is too hot, also return False
      otherwise, this is a nice day, return true
We choose to implement the too cold and too hot tests using macro:
#define   TOO_COLD      80
#define   TOO_HOT       90

#define HOT_DAY(t) ((t) > TOO_HOT) #define COLD_DAY(t) ((t) < TOO_COLD)

Coding of the function is straight forward. Similarly, for print_results(), the algorithm is:
print number of nice days and bad days
      if there are any days counted
            compute the average temperature
            print the average temperature
The resulting code for these functions is shown in Figure 3.10. Notice, we have used the type void for the function print_results(). We will describe this type in more detail later, but it indicates that the function does not return any value. When execution reaches the end of the function body, it simply returns with no value.

Compiling and executing this program with some sample data produces the following sample session:



Previous: 3.3.1 The C Preprocessor
Up: 3.3 Coding Programs for Readability
Next: 3.3.3 Including Header Files
Previous Page: 3.3.1 The C Preprocessor

tep@wiliki.eng.hawaii.edu
Wed Aug 17 08:21:42 HST 1994