1 / 43

The Preprocessor

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:

bibarra
Télécharger la présentation

The Preprocessor

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. The Preprocessor Kernighan/Ritchie: Kelley/Pohl: Chapter 4.11 Chapter 8

  2. Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation

  3. 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"

  4. 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"

  5. 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>

  6. Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation

  7. 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]

  8. 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

  9. 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

  10. Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation

  11. 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

  12. 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

  13. 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

  14. 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

  15. 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

  16. 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);

  17. 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);

  18. 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

  19. 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

  20. 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))

  21. 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

  22. 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))

  23. 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");

  24. Lecture Overview • The use of #include • The use of #define and #undef • Macros with arguments • Conditional compilation

  25. Conditional Compilation • The C preprocessor supports several directives for conditional compilation: • #if • #ifdef • #ifndef • #else • #elif • #endif

  26. 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

  27. #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 ...

  28. #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 ...

  29. #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));

  30. #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)); }

  31. 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

  32. 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 ...

  33. 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 }

  34. 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)); }

  35. 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

  36. 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

  37. 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

  38. 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)

  39. 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

  40. 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)); }

  41. 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

  42. 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"

  43. 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

More Related