Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
151 views

Tips and Tricks Using The Preprocessor (Part One)

The document discusses the C preprocessor and some of its basic features. It introduces macros, the #include directive, conditional compilation using directives like #ifdef and #if, include guards, and the #error directive. The preprocessor allows defining macros, including constants and functions, and conditionally including code. It rewrites source code before compilation, enabling configuration and optimization.

Uploaded by

stephanraza
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
151 views

Tips and Tricks Using The Preprocessor (Part One)

The document discusses the C preprocessor and some of its basic features. It introduces macros, the #include directive, conditional compilation using directives like #ifdef and #if, include guards, and the #error directive. The preprocessor allows defining macros, including constants and functions, and conditionally including code. It rewrites source code before compilation, enabling configuration and optimization.

Uploaded by

stephanraza
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 4

by Anders Lindgren, IAR Systems

Tips and tricks using the preprocessor (part one)



Anyone who has ever read a piece of C source code has seen themthe preprocessor directives. For
example, you can find include directives (#include) at the beginning of most source files. The
preprocessor is a system that rewrites the source before the actual compiler sees it. Clearly, this is a
very powerful toolthe downside is that you could accidentally shoot yourself in the foot.

This is the first article in a two-piece series covering the preprocessor. In this article we will introduced
the preprocessor and covered the basics, including object- and function-like macros, the include
directive, conditional compilation, and end with the two special directives #error and #pragma.

In the next and final article we will cover common preprocessor pitfalls and some advanced
preprocessor topics not normally found in the standard C literature.

The #include directive
The most straight-forward preprocessor directive is #include. When the preprocessor finds this
directive it simply opens the file specified and inserts the content of it, as though the content of the file
would have been written at the location of the directive.

It can take two forms, for example:

#include <systemfile.h>
#include "myfile.h"

The first is used to include standard headers like stdio.h. The latter is for your own application-specific
headers.

Macros
One of most useful features of the preprocessor is to allow the user to define macros, which simply is an
identifier that is mapped to a piece of source code. Whenever the preprocessor finds the macro in the
application source code it replaces the macro with the definition.

Basically, there are two types of macros, object-like macros and function-like macros, the difference is
that function-like macros have parameters.

By convention, macro names are written using upper-case only. The only exception is when a macro is
used to replace something that should have been a function but is implemented using a macro for the
sake of efficiency.

The directive #define can be used to define a macro. In the following example, we define
NUMBER_OF_PLAYERS to the constant "2". This is an object-like macro.

#define NUMBER_OF_PLAYERS 2

int current_score[NUMBER_OF_PLAYERS];

Page 2





Here, the function-like macro PRINT_PLAYER is mapped a more complex piece of source code.

#define PRINT_PLAYER(no) printf("Player %d is named %s", no, names[no])

The definition of a macro should, technically, be specified on a single source line. Fortunately, the C
standard allows you to end a line with a backslash and continue on the next physical line. For example:

#define A_MACRO_THAT_DOES_SOMETHING_TEN_TIMES \
for (i = 0; i < 10; ++i)\
{ \
do_something(); \
}

In addition to the macros that a user can define, the C standard specifies a number of predefined and
library-provided macros that could be used. For example, the macro __FILE__ contains the name of the
current source file, as a string.

If you ever would like to make a macro name undefined you can use the #undef directive.

Object-like macros
Object-like macros can be used to replace an identifier in the source code with some kind of
replacement source code.

Typically, macros can be used to declare a constant that could be configured in one location. Also,
constants could be used to make the source code more readable, even if the value is not intended to
change. For example:

#define SQUARE_ROOT_OF_TWO 1.4142135623730950488016887
double convert_side_to_diagonal(double x)
{
return x * SQUARE_ROOT_OF_TWO;
}
double convert_diagonal_to_side(double x)
{
return x / SQUARE_ROOT_OF_TWO;
}

Preprocessor macros could be used for very weird things, since all that the preprocessor does is to
replace an identifier with an arbitrary piece of source code. For example, the following is legal code
(although, you will probably have to answer to your boss if you ever try to write something like this):

#define BLA_BLA );
int test(int x)
{
printf("%d", x BLA_BLA
}


Function-like macros
Function-like macros are macros that take parameters. When you use them they look like a function call.
A function-like macro could look like the following:

#define SEND(x) output_array[output_index++] = x

When the preprocessor finds a function-like macro in your application source code it will replace it with
the definition. The parameters of the macro will be inserted into the resulting source code at the location
of the formal parameter variables.

So, if you write the following:
Page 3





SEND(10)

Then the compiler will see:

output_array[output_index++] = 10

In the second part of this article we will revisit function-like macros and discuss some of the traps that
are easy to fall into.

Conditional compilation
One of the most powerful features of the preprocessor is the so-called conditional compilationthis
means that portions of the code could be excluded in the actual compilation under the certain
conditions.

This means that your source could contain special code for, say, the ARM processor. Using conditional
compilation, this code could be ignored when compiling for all other processors.

The preprocessor directives #ifdef, #ifndef, #if, #elif, and #else are used to control the
source code. The #ifdef (#ifndef) directive includes a section if a preprocessor symbol is defined
(undefined). For example:

#ifdef ARM_BUILD
__ARM_do_something();
#else
generic_do_something();
#endif

The #if directive can handle any type of integer and logical expression, for example:

#if (NUMBER_OF_PROCESSES > 1) && (LOCKING == TRUE)
lock_process();
#endif

The #elif directive works like a combined #else and #if.

#if and #elif can use the special operator defined to check if a symbol is defined. This is useful in
combination with complex tests, for example:

#if defined(VERSION) && (VERSION > 2)
...
#endif

In part two of this article we will revisit conditional compilation and discuss whether you should prefer
#if:s or #ifdef:s in your application.

Include guards
One typical use for conditional compilation is to ensure that the content of include files are only seen
once. This will not only speed up the compilation, but also ensures that the compiler will not issue an
error (e.g. for redeclaration of a struct) if the header file is included twice.

An include guard typically looks like the following:

#ifndef MYHEADER_H
#define MYHEADER_H

/* The content of the header file goes here. */

#endif
Page 4






Clearly, the first time the header file is included the symbol MYHEADER_H is not defined and the content is
included in the compilation. The second time the header file is read the symbol is defined and the
content is excluded.

Error directives
The #error directive can be used to generate a compiler error message. This is useful when performing
consistency checks, for example:

#if USE_COLORS && !HAVE_DISPLAY
#error "You cannot use colors unless you have a display"
#endif

The #pragma directive
Another preprocessor directive is #pragma. This directive allows the programmer to control the behavior
of the compiler and gives compiler vendors the opportunity to implement extensions to the C language.

The #pragma directice is not covered further in this article since it has little to do with the main task of
the preprocessor.

Conclusion
This is the first article in a two-piece series covering the preprocessor. In this article we have introduced
the preprocessor and covered the basics, including object- and function-like macros, the #include
directive, conditional compilation, and ended with the two special directives #error and #pragma.

In the next and final article we will cover common preprocessor pitfalls and some advanced
preprocessor topics not normally found in the standard C literature.

You might also like