The preprocessor processes a file before it is sent to the compiler.
The IC preprocessor allows definition of macros, and conditional
compilation of sections of code. Using preprocessor macros for
constants and function macros can make IC code more efficient as
well as easier to read. Using #if
to conditionally compile
code can be very useful, for instance, for debugging purposes.
Preprocessor macros are defined by using the #define
preprocessor directive at the start of a line. The scope of IC
macros is to globals and functions. If a macro is defined anywhere in
any of the files loaded into IC, it can be used anywhere in any
file. The following example shows how to define preprocessor macros.
#define RIGHT_MOTOR 0 #define LEFT_MOTOR 1 #define GO_RIGHT(power) (motor(RIGHT_MOTOR,(power))) #define GO_LEFT(power) (motor(LEFT_MOTOR,(power))) #define GO(left,right) {GO_LEFT(left); GO_RIGHT(right);} void main() { GO(0,0); }
Preprocessor macro definitions start with the #define
directive
at the start of a line, and continue to the end of the line. After
#define
is the name of the macro, such as RIGHT_MOTOR
.
If there is a parenthesis directly after the name of the macro, such
as the GO_RIGHT
macro has above, then the macro has arguments.
The GO_RIGHT
and GO_LEFT
macros each take one argument.
The GO
macro takes two arguments. After the name and the
optional argument list is the body of the macro.
Each time a macro is invoked, it is replaced with its body. If the macro has arguments, then each place the argument appears in the body is replaced with the actual argument provided.
Invocations of macros without arguments look like global variable references. Invocations of macros with arguments look like calls to functions. To an extent, this is how they act. However, macro replacement happens before compilation, whereas global references and function calls happen at run time. Also, function calls evaluate their arguments before they are called, whereas macros simply perform text replacement. For example, if the actual argument given to a macro contains a function call, and the macro instantiates its argument more than once in its body, then the function would be called multiple times, whereas it would only be called once if it were being passed as a function argument instead.
Appropriate use of macros can make IC programs more efficient and easier to read. It allows constants to be given symbolic names without requiring storage and access time as a global would. It also allows macros with arguments to be used in cases when a function call is desirable for abstraction, without the performance penalty of calling a function.
Macros defined in files can be used at the command line. Macros can
also be defined at the commandline to be used in interactively, but
these will not affect loads or compilation. To obtain a list of the
currently defined macros, type list defines
at the IC prompt.
It is sometimes desirable to conditionally compile code. The primary
example of this is that you may want to perform debugging output
sometimes, and disable it at others. The IC preprocessor provides a
convenient way of doing this by using the #ifdef
directive.
void go_left(int power) { GO_LEFT(power); #ifdef DEBUG printf("Going Left\n"); beep(); #endif }
In this example, when the macro DEBUG
is defined, the debugging
message "Going Left" will be printed and the board will beep each
time go_left
is called. If the macro is not defined, the
message and beep will not happen. Each #ifdef
must be follwed
by an #endif
at the end of the code which is being
conditionally compiled. The macro to be checked can be anything, and
#ifdef
blocks may be nested.
Unlike regular C preprocessors, macros cannot be conditionally
defined. If a macro definition occurs inside an #ifdef
block,
it will be defined regardless of whether the #ifdef
evaluates
to true or false. The compiler will generate a warning if macro
definitions occur within an #ifdef
block.
The #if
, #else
, and #elif
directives are also
available, but are outside the scope of this document. Refer to a C
reference manual for how to use them.
The way in which IC deals with loading multiple files is
fundamentally different from the way in which it is done in standard
C. In particular, when using standard C, files are compliled
completely independently of each other, then linked together. In
IC, on the other hand, all files are compiled together. This is why
standard C needs function prototypes and extern
global
definitions in order for multiple files to share functions and
globals, while IC does not.
In a standard C preprocessor, preprocessor macros defined in one C
file cannot be used in another C file unless defined again. Also, the
scope of macros is only from the point of definition to the end of the
file. The solution then is to have the prototypes, extern
declarations, and macros in header files which are then included at
the top of each C file using the #include
directive. This
style interacts well with the fact that each file is compiled
independent of all the others.
However, since declarations in IC do not file scope, it would be
inconsistent to have a preprocessor with file scope. Therefore, for
consistency it was desirable to give IC macros the same behavior as
globals and functions. Therefore, preprocessor macros have global
scope. If a macro is defined anywhere in the files loaded into IC,
it is defined everywhere. Therefore, the #include
and
#undef
directives
did not seem to have any appropriate purpose, and were accordingly
left out.
The fact that #define
directives contained within #if
blocks are defined regardless of whether the #if
evaluates to
be true or false is a side effect of making the preprocessor macros
have global scope.
Other than these modifications, the IC preprocessor should be compatible with regular C preprocessors.