The Pre-Processor and standard libraries - The #ifdef and #endif Directives

Chapter chap8 section 11

The next most useful preprocessor directives are ifdef and endif . These are always used in pairs with the basic syntax

	#ifdef  identifier
	.
	.
	.
	#endif

All the code between the #ifdef and the #endif is skipped (i.e. completely ignored) unless the identifier has been the subject of a #define directive or has been defined in some other way.

#ifdef may be written as

#if defined ...

and #ifndef may be written as

#if !defined ...

defined is a special unary operator only understood by the pre-processor. An obvious and useful example is shown below. This program is called debug .

#include	<stdio.h>
#include	<ctype.h>
#define	DEBUG	1
main()
{
	char	inbuf[BUFSIZ];
	int	i = 0;
	int	lcnt = 0;
	gets(inbuf);
	while(*(inbuf+i))
	{
#ifdef	DEBUG
		printf("Character %d Value %c(%o)\n",
			i,*(inbuf+i),*(inbuf+i));
#endif
		if(isalpha(*(inbuf+i)))lcnt++;
#ifdef	DEBUG
		printf("Letter Count %d\n",lcnt);
#endif
		i++;
	}
	printf("Total letters %d\n",lcnt);
}
A typical dialogue is shown.
$ debug
the cat
Character 0 Value t(164)
Letter Count 1
Character 1 Value h(150)
Letter Count 2
Character 2 Value e(145)
Letter Count 3
Character 3 Value  (40)
Letter Count 3
Character 4 Value c(143)
Letter Count 4
Character 5 Value a(141)
Letter Count 5
Character 6 Value t(164)
Letter Count 6
Total letters 6
If the line defining DEBUG were removed or commented out then the diagnostic printf() function calls would not be seen by the compiler and the diagnostic printing would be omitted. This is a common practice.

There are two other ways in which an identifier associated with #define may become defined. The first method is from the compiler command line. A typical Unix system call would be

cc debug.c -DDEBUG

this forces the compiler to see DEBUG as defined even though no actual value is associated with DEBUG . The usage

cc debug.c -DDEBUG=1

may be used to associate a particular value with DEBUG . The compiler command line definition of an identifier is a particularly useful thing to do because it means that debugging code can be switched on and off without making any alterations whatsoever to the source code.

The second method applies only to certain special symbols. There are certain symbols that are defined internally by the preprocessor. The ANSI standard symbols predefined in this way are

symbolmeaning
__STDC__ defined if the compiler is ANSI
__LINE__ the current source line number
__FILE__ the source file name
__DATE__ the date when the program was compiled
__TIME__ the time when the program was compiled

In case your printer or display didn't make it clear these all consist of four letters preceded by and followed by a pair of underline characters. All except __STDC__ and __LINE__ are character strings complete with enclosing double quotes. The following might be found in a program

#ifdef  __STDC__
double  reciprocal(double x)
#else
double  reciprocal(x)
double  x;
#endif
{
	return 1.0/x;
}

providing a function definition acceptable to both ANSI and pre-ANSI compilers.

The logic associated with #ifdef may be extended using #else and #elif is a fairly obvious way. You may well encounter code such as

	#ifdef  UNIX
	#define INTBITS 32
	#else
	#define INTBITS 16
	#endif

This sort of thing is common in programs intended to be ported across a wide range of systems although some of the effects can be achieved more cleanly using limits.h if ANSI compilers are available.

To provide greater flexibility any #define d identifier can be undefined by the directive #undef followed by the identifier. The directive #line forces the compiler to have a particular idea of the current line number in the source. This is only useful in conjunction with __LINE__.

The directive #pragma allows special compiler specific information to be included in the program. The meanings of pragmas are always compiler specific, they may, for example, be used to specify a particular way of storing pointers or generating code.


Exercises