440 likes | 504 Views
The Preprocessor. Kernighan/Ritchie: Kelley/Pohl:. Chapter 4.11 Chapter 8. Lecture Overview. The use of #include The use of #define and #undef Macros with arguments Conditional compilation. The Use of #include. The #include preprocessing directive:
E N D
The Preprocessor Kernighan/Ritchie: Kelley/Pohl: Chapter 4.11 Chapter 8
Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation
The Use of #include • The #include preprocessing directive: tells the preprocessor to replace the line with the contents of the named file • There are two slightly different formats: #include <filename> #include <filename> #include "filename"
The Use of #include • When the quoted form is used: searching for the file begins in the current directory (where the source file was found) • If the file is not found there, the search continues in system dependant directories • Typically, the standard header files are found in /usr/include #include "filename"
The Use of #include • If the file name is enclosed in '<' and '>': only the system dependant directories are searched, and not the current directory • Normally, system header files will be included using '<' and '>' • User defined header files should be quoted (surrounded by '"') #include <filename>
Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation
The Use of #define • The #define preprocessing directive: tells the preprocessor to replace every occurrence of identifier by value • The value is optional – if it is not supplied, then identifier is defined but has no value – can be used as a compilation flag #define identifier [value]
The Use of #undef • The #undef preprocessing directive: cancels the definition of identifier • Must be used before redefining the value of a previously defined identifier • Also useful for unsetting compilation flags #undef identifier
The Use of #undef– Example • The following lines will cause the compiler to issue a warning: • This will compile cleanly: #define SOME_VALUE 5 #define SOME_VALUE 6 #define SOME_VALUE 5 #undef SOME_VALUE #define SOME_VALUE 6
Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation
Macros with Arguments • The general form of a macro definition: • Important: There can be no space between the macro name and the left parenthesis • Example: • Note: This definition has problems (see next) #define macro(parm1, parm2, …) value #define MAX(A, B) A > B? A: B
The Pros and Cons of Macros • Advantages: • Efficiency – the code is inserted directly into the program in compilation time, and therefore no function call is performed • Disadvantages: • Executable size – using a macro is equivalent to duplicating a function wherever it is used • Potential for bugs – see next slides
The Pros and Cons of Macros • A macro definition, like any use of the #define directive, has no type checking • Advantage – the same macro can be used for different types • Disadvantage – code that is not type-safe increases the potential for bugs
Wrong! Macros – Example • The following program prints the larger of two integers: #include <stdio.h> #define MAX(A, B) A > B? A: B int main() { int a = 5, b = 3; printf ("max: %d\n", MAX (a, b)); return 0; } max: 5
Wrong! Macros – Problem 1 • This, however, will not work: #include <stdio.h> #define MAX(A, B) A > B? A: B int main() { int a = 5, b = 3; printf ("max: %d\n", MAX (a, b)); printf ("max + 1: %d\n", MAX (a, b) + 1); return 0; } max: 5 max + 1: 5
Macros – Problem 1 • What happened? • When the macro was replaced by the preprocessor, this line of code: was replaced with this one: printf ("max + 1: %d\n", MAX(a, b) + 1); printf ("max + 1: %d\n", a > b? a: b + 1);
Macros – Solution 1 • The solution – add parenthesis to the macro definition: • Now, this line: is replaced with this one: #define MAX(A, B) (A > B? A: B) printf ("max + 1: %d\n", MAX(a, b) + 1); printf ("max + 1: %d\n", (a > b? a: b) + 1);
Wrong! Macros – Problem 2 • We start by defining a macro that computes the square of a number #include <stdio.h> #define SQ(X) (X * X) int main() { int a = 5; printf ("%d squared: %d\n", a, SQ (a)); return 0; } 5 squared: 25
Wrong! Macros – Problem 2 • If the argument is an expression, we get another form of the previous problem: #include <stdio.h> #define SQ(X) (X * X) int main() { int a = 5, b = 3; printf ("%d squared: %d\n", a + b, SQ (a + b)); return 0; } 8 squared: 23
Macros – Solution 2 • The expression: • Was replaced with: • As before, the problem is solved by using more parenthesis: SQ (a + b) (a + b * a + b) #define SQ(X) ((X) * (X))
Wrong! Macros – Problem 3 • This time the macro MAX is defined safely, but still we get incorrect results #include <stdio.h> #define MAX(A, B) (((A) > (B))? (A): (B)) int main() { int a = 5, b = 3; printf ("max: %d\n", MAX (a, b)); printf ("max + 1: %d\n", MAX (++a, ++b)); return 0; } max: 5 max + 1: 7
Macros – Solution 3? • In this case, the expression: • Was replaced with: • The expression '++a' is executed twice! • The only solution to this problem is to avoid expressions with side effects MAX (++a, ++b) (((++a) > (++b))? (++a): (++b))
More Uses of Macros • Macros can be used as shortcuts: • To define macros longer than a single line, use '\' at the end of each line but the last #define forever for (;;) #define INC(A) \ if ((A) < 100) \ (A)++; \ else \ printf ("Error: overflow.\n");
Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation
Conditional Compilation • The C preprocessor supports several directives for conditional compilation: • #if • #ifdef • #ifndef • #else • #elif • #endif
The Use of #ifdef • The most useful conditional compilation directive is: • The code that follows an #ifdef directive will compile depending on whether identifier is defined or not • This will affect any line of code until the directive #endif is encountered #ifdef identifier
#ifdef– Example • Consider the following program: #include <stdio.h> int main() { int i; for (i = 5; i < 10; i++) { printf ("i: %3d, i^2: %3d\n", i, i * i); } return 0; } i: 5, i^2: 25 i: 6, i^2: 36 ...
#ifdef– Example • We make a small change: #include <stdio.h> #include <math.h> int main() { int i; for (i = 5; i < 10; i++) { printf ("i: %3d, i^2: %3d\n", i, pow(i, 2)); } return 0; } i: 5, i^2: 0 i: 6, i^2: 0 ...
#ifdef– Example • To fix the bug, the line: should be changed to: • To try and find the bug, we could add printf lines to check various values printf ("i: %3d, i^2: %3d\n", i, pow(i, 2)); printf ("i: %3d, i^2: %3d\n", i, (int)pow(i, 2));
#ifdef– Example • We change the main loop to: int di; float df; ... for (i = 5; i < 10; i++) { #ifdef DEBUG printf ("DEBUG PRINT: i: %d\n", i); di = pow (i, 2); df = pow (i, 2); printf ("DEBUG PRINT: di: %d\n", di); printf ("DEBUG PRINT: df: %d\n", df); #endif printf ("i: %3d, i^2: %3d\n", i, pow(i, 2)); }
Conditional Compilation Using Command Line Options • This solution is useful, because we can add code for debugging, but this code will not be part of the final executable • The downside – to switch from debug tonon-debug mode, we need to change the source file, by adding or removing the line: #define DEBUG
Conditional Compilation Using Command Line Options • The C compiler provides an option to define identifiers directly from the command line, without changing the code: • In the previous example, instead of inserting the #define line into the source file, we can compile it with: gcc –D name[=definition] gcc –D DEBUG ...
Debug Printing • Another option that is commonly usedis to write a general function for printing debug information: • Conditional compilation is only done here, and not anywhere else in the program void debug_print (int i) { #ifdef DEBUG printf ("DEBUG PRINT: %d\n", i); #endif }
Debug Printing • Using the debug_print() function, we can rewrite our program: • No need for any #ifdef's here for (i = 5; i < 10; i++) { debug_print (i); di = pow (i, 2); df = pow (i, 2); debug_print (di); debug_print (df); printf ("i: %3d, i^2: %3d\n", i, pow(i, 2)); }
Debug Printing • The above adds unnecessary function calls even when the DEBUG flag is turned off • Using a macro instead of a function prevents this inefficiency: #ifdef DEBUG #define debug_print(a) \ printf ("DEBUG PRINT: %d\n", (int)a); #else #define debug_print(a) #endif
Generating Compilation Errors • A major disadvantage of defining values in the command line is that the programmer has no control over the defined values • The #error directive can be used to deliberately generate a compilation error: #if (EDGE_SIZE < 2) #error EDGE_SIZE must be at least 2. #endif
The Use of #if • The #ifdef directive is just a special case of the more general #if directive • The parameter for #if is any constant expression (variables are not allowed) • May use the defined operator: is equivalent to #if defined(DEBUG) #ifdef DEBUG
The Use of #if • With #if it is possible to create more complex expressions: • Multiple cases can be handled using the directives #else and #elif (a short-cut for else-if) #if defined(HP9000) || defined(SUN4)
Blocking Out Code Segments • Sometimes, during development, we need to comment-out parts of the code • This is usually done by surrounding it with '/*' and '*/' • However, this will generate a syntax error if the code already contains comments
Blocking Out Code Segments • The problem can be solved using #if: int di; float df; ... for (i = 5; i < 10; i++) { #if 0 printf ("DEBUG PRINT: i: %d\n", i); di = pow (i, 2); df = pow (i, 2); printf ("DEBUG PRINT: di: %d\n", di); printf ("DEBUG PRINT: df: %d\n", df); #endif printf ("i: %3d, i^2: %3d\n", i, pow(i, 2)); }
Conditional Inclusion • Another use of conditional compilationis to make sure that header files are only included once • The problem occurs when header files are included by other header files, causing multiple inclusions of the same file • As a result, the same code might be compiled twice, causing a compilation error
Conditional Inclusion – The Problem • File 'my_int.h': • File 'other.h': • File 'my_prog.c': typedef int Integer; #include "my_int.h" #include "my_int.h" #include "other.h"
Solution – #include guard • We surround the contents of the header file with conditional directives • For example, the header file 'my_int.h' will look like this: #ifndef _MY_INT #define _MY_INT typedef int Integer; #endif