Go to the first, previous, next, last section, table of contents.

Preprocessor

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

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.

Conditional compilation

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.

Comparison with regular C preprocessors

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.


Go to the first, previous, next, last section, table of contents.