The Pre-Processor and standard libraries - Function Like Macros

Chapter chap8 section 10

An extension of the #define directive allows parameters to be associated with preprocessor directives in a notation very reminiscent of a function. These are normally called macros or function-like macros . getchar(), putchar() and the isathing() macros are examples that are defined in the header files stdio.h and ctype.h respectively.

The basic syntax is

#define identifier(identifier list) replacement-string

A simple example might be

#define cbroot(x) pow(x,1.0/3)

this means that code such as

cbroot(y+17)

is converted into

pow(y+17,1.0/3)

by the preprocessor. This is very handy, however there are some problems. Consider the example

#define square(a) a*a

This is quite satisfactory for usages such as

square(17)

which is converted to

17*17

but the conversion of

square(x+2)

to

x+2*x+2

is unlikely to have the effect that the programmer intended. To avoid this problem it is essential to write

#define square(a) (a)*(a)

Once this has been done the preprocessor will convert

square(x+2)

to

(x+2)*(x+2)

Of course there are still some problems with usages such as

1/square(x+2)

being converted to

1/(x+2)*(x+2)

which always has the value 1. To solve this problem a macro such as square is always defined as

#define square(a) ((a)*(a))

the immediate previous example converting to

1/((x+2)*(x+2))

Unfortunately there are some problems that cannot be resolved. Consider the following program.

#define	square(x)	((x)*(x))
main()
{
	int	i=0;
	while(i<16)
		printf("%2d %4d\n",i,square(i++));
}
The output produced is
 2    0
 4    6
 6   20
 8   42
10   72
12  110
14  156
16  210
There is clearly something seriously wrong here. The loop has gone up in steps of two rather than one and the numbers in the right hand column aren't even squares. The macro expansion of "square(i++)" is

((i++)*(i++))

which, of course, increments "i" twice not once. Note also the right to left order of function parameter evaluation. This sort of problem causes most C programmers to use function-like macros with considerable care.

Of course macro definitions within comments are not seen by either the compiler or the pre-processor; comments are stripped from the source program before pre-processing. Macros and #define' d identifiers are not seen when they are referenced inside strings however the value associated with a pre-processor definition may be a string.

Also if the formal parameter associated with a macro is preceded by a "#" symbol in the replacement string then a string complete with enclosing quotes and all relevant escapes is formed. This operation is known as stringizing. It is illustrated by the following program.

#define	string(x)	#x
main()
{
	printf(">>%s<<\n",string(a string));
	printf(">>%s<<\n",string(" quoted "));
}
producing the output
>>a string<<
>>" quoted "<<
The following example is more interesting. It also shows that a macro can be defined in terms of another macro which is in turn defined in terms of yet another macro.
#define	showx(x)	printf( #x " = %d  ",(x))
#define	show1(x)	showx(x);printf("\n")
#define	show2(x,y)	showx(x);show1(y)
#define	show3(x,y,z)	showx(x);show2(y,z)
#define	show4(x,y,z,p)	showx(x);show3(y,z,p)

main()
{
	int	a = 4;
	int	b = 9;
	int	c = 7;
	int	d;
	show1(c+d);
	show3(a,b+c,d=a+b);
	show4(a*b,b*c,c*d,d*a);
}
producing the output
c+d = 7  
a = 4  b+c = 16  d=a+b = 13  
a*b = 36  b*c = 63  c*d = 91  d*a = 52  
It might be useful to consider the steps in the expansion of show1(c+d) in the above program. The first step of macro expansion conversion changes this to

	"showx(c+d);printf("\n");

The expansion of showx() yields

	printf("c+d" " = %d  ",(c+d))

after the stringizing indicated by "#x". The rule concerning the concatenation of adjacent strings separated only by white space then operates to give a single layout specification. Warning - many supposed ANSI compilers don't seem to get this quite right, it should be used with care.

If it is not possible or convenient to write the complete text of a function-like macro on one line then it can be written over several lines if there is an escaping backslash immediately before the end of each line.

The pre-processor also provides facilities to paste together tokens to form a single token after pre-processing. This is called token pasting and is achieved using the ## symbol. This simply means join up the two items on either side of the ## as if they were a single text token. Token pasting is illustrated by the following program

#include	<stdio.h>
#define	shown(x)	printf("%d",n##x)
main()
{
	int	n1 = 3;
	int	n2 = 4;
	int	n3 = 5;
	shown(1);
	shown(2);
	shown(3);
	putchar('\n'); 
}
producing the output
345
When the pre-processor processed shown(1) it generated

	printf("%d",n1)

rather than the

	printf("%d",n 1)

that would have been generated without pasting.

There are further complex rules defining the behaviour of the preprocessor when handling replacement strings which include identifiers that were #define' d in an earlier pre-processor directive.


The #ifdef and #endif directives