C Pre Processor Directives

You might also like

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 12

C compilers incorporate a preprocessing phase that alters the source code in various ways before passing it on for compiling.

Four capabilities are provided by this facility in C. They are: macro processing inclusion of text from other files conditional compiling in-line assembly language The preprocessor is controlled by directives which are not part of the C language proper. Each directive begins with a #character and is written on a line by itself. Only the preprocessor sees these directive lines since it deletes them from the code stream after processing them. Depending on the compiler, the preprocessor may be a separate program or it may be integrated into the compiler itself. C has an integrated preprocessor that operates at the front end of its single pass algorithm. Macro Processing We use macros for three reasons. 1) To save time we can define a macro for long sequences that we will need to repeat many times. 2) To clarify the meaning of the software we can define a macro giving a symbolic name to a hard-to-understand sequence. The I/O port #define macros are good examples of this reason. 3) To make the software easy to change, we can define a macro such that changing the macro definition, automatically updates the entire software.
#define Name CharacterString?...

define names which stand for arbitrary strings of text. After such a definition, the preprocessor replaces each occurrence of Name (except in string constants and character constants) in the source text with CharacterString?.... As C implements this facility, the term macro is misleading, since parameterized substitutions are not supported. That is, CharacterString?... does not change from one substitution to another according to parameters provided with Name in the source text. C accepts macro definitions only at the global level. The Name part of a macro definition must conform to the standard C naming conventions as described in Chapter 2. CharacterString?... begins with the first printable character following Name and continues through the last printable character of the line or until a comment is reached. If CharacterString?... is missing, occurrences of Name are simply squeezed out of the text. Name matching is based on the whole name (up to 8 characters); part of a name will not match. Thus the directive

#define size 10

will change
short data[size];

into
short data[10];

but it will have no effect on


short data[size1];

Replacement is also performed on subsequent #define directives, so that new symbols may be defined in terms of preceding ones. The most common use of #define directives is to give meaningful names to constants; i.e., to define so called manifest constants. However, we may replace a name with anything at all, a commonly occurring expression or sequence of statements for instance. To disable interrupt during a critical section we could implement.
#define START_CRITICAL asm(" tpa\n staa %SaveSP\n sei") #define END_CRITICAL asm(" ldaa %SaveSP\n tap") void function(void) {unsigned char SaveSP; START_CRITICAL; /* make atomic, entering critical section */ /* we have exclusive access to global variables */ END_CRITICAL; /* end critical section */ }

Listing 11.1: Example of #define

Conditional Compiling This preprocessing feature lets us designate parts of a program which may or may not be compiled depending on whether or not certain symbols have been defined. In this way it is possible to write into a program optional features which are chosen for inclusion or exclusion by simply adding or removing #define directives at the beginning of the program. When the preprocessor encounters
#ifdef Name

it looks to see if the designated name has been defined. If not, it throws away the following source lines until it finds a matching
#else

or
#endif

directive. The #endif directive delimits the section of text controlled by #ifdef, and the #else directive permits us to split conditional text into true and false parts. The first part (#ifdef...#else) is compiled only if the designated name is defined, and the second (#else...#endif) only if it is not defined. The converse of #ifdef is the
#ifndef Name

directive. This directive also takes matching #else and #endif directives. In this case, however, if the designated name is not defined, then the first ( #ifndef...#else) or only (#ifndef...#endif) section of text is compiled; otherwise, the second (#else...#endif), if present, is compiled. Nesting of these directives is allowed; and there is no limit on the depth of nesting. It is possible, for instance, to write something like
#ifdef ABC ... /* ABC */ #ifndef DEF ... /* ABC and #else ... /* ABC and #endif ... /* ABC */ #else ... /* not ABC #ifdef HIJ ... /* not ABC #endif ... /* not ABC #endif

not DEF */ DEF */

*/ but HIJ */ */

Listing 11.2: Examples on conditional compilation where the ellipses represent conditionally compiled code, and the comments indicate the conditions under which the various sections of code are compiled. A good application of conditional compilation is inserting debugging instrumemts. In this example the only purpose of writing to PORTC is assist in performance

debugging. Once the system is debugged,we can remove all the debugging code, simply by deleting the #define Debug 1 line.
#define Debug 1 int Sub(int j){ int i; #ifdef Debug PORTC|=0x01; /* PC0 set when Sub is entered */ #endif i=j+1; #ifdef Debug PORTC&=~0x01; /* PC0 cleared when Sub is exited */ #endif return(i);} void Program(){ int i; #ifdef Debug PORTC|=0x02; /* PC1 set when Program is entered */ #endif i=Sub(5); while(1) { PORTC=2; i=Sub(i);}} void ProgB(){ int i; i=6; #ifdef Debug PORTC&=~0x02; /* PC1 cleared when Sub is exited */ #endif }

Listing 11.3: Conditional compilation can help in removing all debugging code For more information about debugging see Chapter 2 of Valvano's Embedded Microcomputer Systems: Real Time Interfacing.

Including Other Source Files The preprocessor also recognizes directives to include source code from other files. The two directives
#include "Filename" #include <Filename>

cause a designated file to be read as input to the compiler. The difference between these two directives is where the compiler looks for the file. The <filename> version will search for the file in the standard include directory, while the "filename" version will search for the file in the same directory as the original source file. The preprocessor replaces these directives with the contents of the designated files. When the files are exhausted, normal processing resumes. Filename follows the normal MS-DOS file specification format, including drive, path, filename, and extension.

In Chapter 10, an example using #include was presented that implemented a feature similar to encapsulated objects of C++, including private and public functions.

Interrupt software The ICC11/ICC12 preprocessor also recognizes three pragma directives that we will use to develop interrupt software. We use the interrupt_handler pragma to specify a function as an interrupt handler. The compiler will then use the rti instruction rather than the rts instruction to return from ExtHan.
#pragma interrupt_handler ExtHan() void ExtHan(void){ KWIFJ=0x80; // clear flag PutFifo(PORTJ&0x7F);}

Listing 11.4: Interrupt service routines are specified using a pragma in ICC11/ICC12. We use the abs_address and end_abs_address pragmas to define the interrupt vector.
#pragma abs_address:0xffdo void (*KeyWakupJ_interrupt_vector[])() = { ExtHan}; /* 812 KeyWakeUpJ */ #pragma end_abs_address

Listing 11.5: Pragmas allow us to define interrupt vectors in ICC11/ICC12. We also set the reset vector using the abs_address and end_abs_address pragmas.
extern void _start(); /* entry point in crt12.s */ #pragma abs_address:0xfffe void (*Reset_interrupt_vectors[])() = { _start }; /* fffe RESET, entry point into ICC12 */ #pragma end_abs_address

Listing 11.6: Pragmas allow us to define the reset vector in ICC11/ICC12. We will not use pragmas to develop interrupt software with the Metrowerks compiler. We use the interrupt key word to specify a function as an interrupt handler. The Metrowerks compiler will then use the rti instruction rather than the rts instruction to return from ExtHan. We start counting the interrupt number from reset. Some of the interrupt numbers used by Metrowerks for the MC68HC812A4 are shown in the following table.

number 24 23 20 16 15 8 6 4 0

source Key wakeup H Key wakeup J SCI0 timer overflow timer channel 7 timer channel 0 Key wakeup D SWI software interrupt reset

Table 11-1: Interrupt numbers for the MC68HC812A4 used by Metrowerks


0xFFD6 0xFFDE 0xFFE0 0xFFE2 0xFFE4 0xFFE6 0xFFE8 0xFFEA 0xFFEC 0xFFEE 0xFFF0 0xFFF6 0xFFF8 0xFFFE interrupt interrupt interrupt interrupt interrupt interrupt interrupt interrupt interrupt interrupt interrupt interrupt interrupt interrupt 20 16 15 14 13 12 11 10 9 8 7 4 3 0 SCI timer overflow timer channel 7 timer channel 6 timer channel 5 timer channel 4 timer channel 3 timer channel 2 timer channel 1 timer channel 0 real time interrupt SWI software int trap software int reset

Table 11-2: Interrupt numbers for the 9S12C32 used by Metrowerks Metrowerks will automatically set the interrupt vector for KeyWakeup J to point to the ExtHan routine.
void interrupt 23 ExtHan(void){ KWIFJ=0x80; // clear flag PutFifo(PORTJ&0x7F);}

Listing 11.7: Interrupt service routines are specified in Metrowerks. We use the prm linker file to define the reset vector.
LINK keywake.abs NAMES keywake.o start12s.o ansis.lib END SECTIONS MY_RAM = READ_WRITE 0x0800 TO 0x0AFF; MY_ROM = READ_ONLY 0xF000 TO 0xFF00; MY_STK = READ_WRITE 0x0B00 TO 0x0BFF; PLACEMENT DEFAULT_ROM INTO MY_ROM; DEFAULT_RAM INTO MY_RAM; SSTACK INTO MY_STK; END /* set reset vector to function _Startup defined in startup code start12.c

*/ VECTOR ADDRESS 0xFFFE _Startup

Listing 11.8: The last line of the PRM linker file defines the reset vector in Metrowerks.

Preprocessor directives
Preprocessor directives are lines included in the code of our programs that are not program statements but directives for the preprocessor. These lines are always preceded by a hash sign (#). The preprocessor is executed before the actual compilation of code begins, therefore the preprocessor digests all these directives before any code is generated by the statements. These preprocessor directives extend only across a single line of code. As soon as a newline character is found, the preprocessor directive is considered to end. No semicolon (;) is expected at the end of a preprocessor directive. The only way a preprocessor directive can extend through more than one line is by preceding the newline character at the end of the line by a backslash ( \).

macro definitions (#define, #undef)


To define preprocessor macros we can use #define. Its format is: #define identifier replacement When the preprocessor encounters this directive, it replaces any occurrence of identifier in the rest of the code by replacement. This replacement can be an expression, a statement, a block or simply anything. The preprocessor does not understand C++, it simply replaces any occurrence of identifier by replacement.

1 #define TABLE_SIZE 100 2 int table1[TABLE_SIZE]; 3 int table2[TABLE_SIZE];


After the preprocessor has replaced TABLE_SIZE, the code becomes equivalent to:

1 int table1[100]; 2 int table2[100];


This use of #define as constant definer is already known by us from previous tutorials, but #define can work also with parameters to define function macros:

#define getmax(a,b) a>b?a:b


This would replace any occurrence of getmax followed by two arguments by the replacement expression, but also replacing each argument by its identifier, exactly as you would expect if it was a function:

1 // function macro 2 #include <iostream> 3 using namespace std; 4 5 #define getmax(a,b) ((a)>(b)?(a):(b)) 6 7 int main() 8{ 9 int x=5, y; 10 y= getmax(x,2); 11 cout << y << endl; 12 cout << getmax(7,x) << endl; 13 return 0; 14 }

5 7

Defined macros are not affected by block structure. A macro lasts until it is undefined with the #undef preprocessor directive:

1 #define TABLE_SIZE 100 2 int table1[TABLE_SIZE]; 3 #undef TABLE_SIZE 4 #define TABLE_SIZE 200 5 int table2[TABLE_SIZE];
This would generate the same code as:

1 int table1[100]; 2 int table2[200];


Function macro definitions accept two special operators ( # and ##) in the replacement sequence: If the operator # is used before a parameter is used in the replacement sequence, that parameter is replaced by a string literal (as if it were enclosed between double quotes)

1 #define str(x) #x 2 cout << str(test);


This would be translated into:

cout << "test";


The operator ## concatenates two arguments leaving no blank spaces between them:

1 #define glue(a,b) a ## b 2 glue(c,out) << "test";


This would also be translated into:

cout << "test";


Because preprocessor replacements happen before any C++ syntax check, macro definitions can be a tricky feature, but be careful: code that relies heavily on complicated macros may seem obscure to other programmers, since the syntax they expect is on many occasions different from the regular expressions programmers expect in C++.

Conditional inclusions (#ifdef, #ifndef, #if, #endif, #else and #elif)


These directives allow to include or discard part of the code of a program if a certain condition is met. #ifdef allows a section of a program to be compiled only if the macro that is specified as the parameter has been defined, no matter which its value is. For example:

1 #ifdef TABLE_SIZE 2 int table[TABLE_SIZE];

3 #endif
In this case, the line of code int table[TABLE_SIZE]; is only compiled if TABLE_SIZE was previously defined with #define, independently of its value. If it was not defined, that line will not be included in the program compilation. #ifndef serves for the exact opposite: the code between #ifndef and #endif directives is only compiled if the specified identifier has not been previously defined. For example:

1 #ifndef TABLE_SIZE 2 #define TABLE_SIZE 100 3 #endif 4 int table[TABLE_SIZE];


In this case, if when arriving at this piece of code, the TABLE_SIZE macro has not been defined yet, it would be defined to a value of 100. If it already existed it would keep its previous value since the #define directive would not be executed. The #if, #else and #elif (i.e., "else if") directives serve to specify some condition to be met in order for the portion of code they surround to be compiled. The condition that follows #ifor #elif can only evaluate constant expressions, including macro expressions. For example:

1 #if TABLE_SIZE>200 2 #undef TABLE_SIZE 3 #define TABLE_SIZE 200 4 5 #elif TABLE_SIZE<50 6 #undef TABLE_SIZE 7 #define TABLE_SIZE 50 8 9 #else 10 #undef TABLE_SIZE 11 #define TABLE_SIZE 100 12 #endif 13 14 int table[TABLE_SIZE];
Notice how the whole structure of #if, #elif and #else chained directives ends with #endif. The behavior of #ifdef and #ifndef can also be achieved by using the special operators defined and !defined respectively in any #if or #elif directive:

1 #if !defined TABLE_SIZE 2 #define TABLE_SIZE 100 3 #elif defined ARRAY_SIZE 4 #define TABLE_SIZE ARRAY_SIZE 5 int table[TABLE_SIZE]; 6 #endif

Line control (#line)


When we compile a program and some error happens during the compiling process, the compiler shows an error message with references to the name of the file where the error happened and a line number, so it is easier to find the code generating the error.

The #line directive allows us to control both things, the line numbers within the code files as well as the file name that we want that appears when an error takes place. Its format is: #line number "filename" Where number is the new line number that will be assigned to the next code line. The line numbers of successive lines will be increased one by one from this point on. "filename" is an optional parameter that allows to redefine the file name that will be shown. For example:

1 #line 20 "assigning variable" 2 int a?;


This code will generate an error that will be shown as error in file "assigning variable", line 20.

Error directive (#error)


This directive aborts the compilation process when it is found, generating a compilation the error that can be specified as its parameter:

1 #ifndef __cplusplus 2 #error A C++ compiler is required! 3 #endif


This example aborts the compilation process if the macro name __cplusplus is not defined (this macro name is defined by default in all C++ compilers).

Source file inclusion (#include)

This directive has also been used assiduously in other sections of this tutorial. When the preprocessor finds an #include directive it replaces it by the entire content of the specified file. There are two ways to specify a file to be included:

1 #include "file" 2 #include <file>


The only difference between both expressions is the places (directories) where the compiler is going to look for the file. In the first case where the file name is specified between double-quotes, the file is searched first in the same directory that includes the file containing the directive. In case that it is not there, the compiler searches the file in the default directories where it is configured to look for the standard header files. If the file name is enclosed between angle-brackets <> the file is searched directly where the compiler is configured to look for the standard header files. Therefore, standard header files are usually included in angle-brackets, while other specific header files are included using quotes.

Pragma directive (#pragma)

This directive is used to specify diverse options to the compiler. These options are specific for the platform and the compiler you use. Consult the manual or the reference of your compiler for more information on the possible parameters that you can define with #pragma. If the compiler does not support a specific argument for #pragma, it is ignored - no error is generated.

Predefined macro names


macro __LINE__ __FILE__

The following macro names are defined at any time: value Integer value representing the current line in the source code file being compiled. A string literal containing the presumed name of the source file being compiled. A string literal in the form "Mmm dd yyyy" containing the date in which the compilation __DATE__ process began. A string literal in the form "hh:mm:ss" containing the time at which the compilation __TIME__ process began. An integer value. All C++ compilers have this constant defined to some value. If the __cplusplus compiler is fully compliant with the C++ standard its value is equal or greater than 199711L depending on the version of the standard they comply. For example:

1 // standard macro names This is the 2 #include <iostream> Its compilat 3 using namespace std; The compiler 4 5 int main() 6{ 7 cout << "This is the line number " << __LINE__; 8 cout << " of file " << __FILE__ << ".\n"; 9 cout << "Its compilation began " << __DATE__; 10 cout << " at " << __TIME__ << ".\n"; 11 cout << "The compiler gives a __cplusplus value of " << __cplusplus; 12 return 0; 13 }

You might also like