Compiler Preprocessor Directives The C Preprocessor u u

  • Slides: 18
Download presentation
Compiler Preprocessor Directives

Compiler Preprocessor Directives

The C Preprocessor u u The C preprocessor (cpp) changes your source code based

The C Preprocessor u u The C preprocessor (cpp) changes your source code based on instructions, or preprocessor directives, embedded in the source code. The preprocessor creates a “new” version of your program and it is this new program that actually gets compiled. – Normally, you do not see these “new” versions on the hard disk, as they are deleted after compilation. u Each preprocessor directive appears in the source code proceeded by a ‘#’ sign.

The #define Directive u Simple substitution Macros #define text 1 text 2 u u

The #define Directive u Simple substitution Macros #define text 1 text 2 u u This tells the C pre-processor to find all occurrences of “text 1” in the source code and substitute “text 2”. Usually used for constants: #define MAX 1000 – Generally use upper case letters (by convention). – Always separate by white space. – No trailing semi-colon (think about it. . . ) u An example: – #define PRINT printf PRINT(“hello, world”);

Function Macros You can also define more complex macros: #define max(a, b) ( (a)

Function Macros You can also define more complex macros: #define max(a, b) ( (a) > (b) ? (a) : (b) ) …… printf("%d", 2 * max(3+3, 7)); /* is equivalent to */ printf("%d", 2 * ( (3+3) > (7) ? (3+3) : (7) ); u The parentheses are important! For example: #define max(a, b) a>b? a: b printf("%d", 2 * max(3+3, 7)); /*is equivalent to */ printf("%d", 2 * 3+3 > 7 ? 3+3 : 7 ); u

Function Macros Should be Used with Care #define max(x, y) ((x)>(y)? (x): (y)) ……

Function Macros Should be Used with Care #define max(x, y) ((x)>(y)? (x): (y)) …… int n, i=4, j=3; n= max( i++, j); /* Same as n= (( i++ )>( j )? ( i++ ): ( j )) */ printf("%d, %d", n, i, j); u The output is: – 5, 6, 3 u If max was a function, the output would have been: – 4, 5, 3

Conditional Compilation (1) u u The pre-processor directives #if, #else, and #endif tell the

Conditional Compilation (1) u u The pre-processor directives #if, #else, and #endif tell the compiler is the enclosed source code should be compiled Can create more efficient and more portable code. – Compiled to match the environment it is compiled for. u Structure: Any Constant Expression #if condition_1 • non-zero is true statement_block_1 • compile statement_block_1 #elif condition_2 • zero is false statement_block_2 • don't compile statement_block_1. . . #elif condition_n statement_block_n #else default_statement_block #endif

Conditional Compilation (2) u u For the most part, the only things that can

Conditional Compilation (2) u u For the most part, the only things that can be tested are things that can be defined by #define statements. An example: #define ENGLAND 0 #define FRANCE 1 #define ITALY 0 #if ENGLAND #include "england. h" #elif FRANCE #include "france. h" #elif ITALY #include "italy. h" #else #include "canada. h" #endif

Conditional Compilation (3) u Conditional compilation can also be very useful for including “debugging

Conditional Compilation (3) u Conditional compilation can also be very useful for including “debugging code” – When you are debugging your code you probably print out some information during the running of your program. – However, you may not need want these extra print outs when you release your program. So, you need to go back through your code and delete them. u Instead, you can use #if #endif to save you time: #define DEBUG 1 …… #if DEBUG printf("Debug reporting at function my_sort()!n"); #endif ……

Conditional Compilation (4) u Usually people use a preprocessor function as the condition of

Conditional Compilation (4) u Usually people use a preprocessor function as the condition of compilation: defined ( NAME ) v. Returns true if NAME has been defined; else false u An example: #define DEBUG #if defined ( DEBUG ) printf("debug report at function my_sort() n"); #endif u Note: This only depends on if DEBUG has been defined. But has nothing to do with which value DEBUG is defined to. u Can also use the notation #ifdef NAME instead.

Conditional Compilation (5) u u Conditional compilation can also be achieved using #define macros

Conditional Compilation (5) u u Conditional compilation can also be achieved using #define macros An example: #define DEBUG(_x) _x DEBUG(printf("debug report at function my_sort() n")); u u This is much shorter than large #ifdef/#endif directives, so it does not clutter the code as much, and is more likely to be left in the code To disable debugging output simply redefine the macro to: #define DEBUG(_x) u This caused the text within the brackets to be replaced by nothing. That is, it is simply removed by the preproceesor

Conditional Compilation (6) u The #undef … directive makes sure that defined( …) evaluates

Conditional Compilation (6) u The #undef … directive makes sure that defined( …) evaluates to false. u An example: – Suppose at the first part of a source file, you want DEBUG to be defined. At the last part of the file, however, you want DEBUG to be undefined… u. A directive can also be set on the Unix command line at compile time: cc –DDEBUG myprog. c v. Compiles myprog. c with the symbol DEBUG defined as if #define DEBUG was in written at the top of myprog. c.

The #include Directive u u u We've seen lots of these already. This directive

The #include Directive u u u We've seen lots of these already. This directive causes all of the code in the included file to be inserted at the point in the text where #include appears. The included files can contain other #include directive. – Usually limited to 10 levels of nesting u u < > tell the compiler to look in the standard include directories first. " " tells the compiler to treat this as a Unix filename. – Relative to directory containing file if a relative pathname. – Relative to root with an absolute pathname. – But most compilers also search for the standard include directory if it cannot find the file at the specified path.

Inline Functions (1) u Recall the two different ways to compute the maximum number

Inline Functions (1) u Recall the two different ways to compute the maximum number between two integers: – #define max(a, b) ((a)>(b)? (a): (b)) – int max(int a, int b) { return a>b? a: b; } u Function calls need to jump to another part of your program and jump back when done. This needs to: – Save current registers. – Allocate memory on the stack for the local variables in the function that is called. – Other overhead …… u Therefore, the macro approach is often more efficient, since it does not have function call overhead. – But, this approach can be dangerous, as we saw earlier.

Inline Functions (2) u Modern C and C++ compilers provide “inline” functions to solve

Inline Functions (2) u Modern C and C++ compilers provide “inline” functions to solve the problem: – Put the inline keyword before the function header. inline int max(int a, int b) { return a>b? a: b; } u You then use it as a normal function in your source code. – printf( "%d", max( x, y) ); u When the compiler compiles your program, it will not compile it as a function. Rather, it just integrates the necessary code in the line that max() is called in to avoid an actual function call. – The above printf(…) is compiled to be something like: – printf("%d", x>y? x: y);

Inline Functions (2) u u u Or at least, that’s the propaganda In fact,

Inline Functions (2) u u u Or at least, that’s the propaganda In fact, many compilers ignore the inline directive, and decide themselves. On average, compilers usually know more about optimisation than programmers. – They can get a speedup by making their own decision rather than following programmer requests. u This is a rare exception to the usual assumption that the C programmer is competent.

Inline Functions (3) u Writing the small but often-used functions as inline functions can

Inline Functions (3) u Writing the small but often-used functions as inline functions can improve the speed of your program. – In a very extreme example I saw recently, where the program consisted almost entirely of calls to very small functions, inlining reduced the execution time by around 90%. u A problem with inlining is that you have to include the inline function definition before you use it in a file. – For non-inline functions, only the function prototypes are needed. u u But C provides a programming model where each file is compiled separately If the code for a function appears in one file, and it is called from another file, then in general the compiler cannot inline the function to the place where it is called

Inline Functions (3) u There is no good solution to this, only bad ones

Inline Functions (3) u There is no good solution to this, only bad ones – You can use a macro rather than an inline function and put the macro in the header file – You can put the C source code for the inline function in the header file v But there is potentially a problem with the same function being defined multiple times in different files of the program v Recall that including a header file just imports the text of the header into the C source file v You may need to declare the function to be static inline myfunc(. . ) v A static function in C is local to the file where is it declared – None of these solutions is pretty

Inline Functions (3) u u Researchers have developed compiler technology to inline function calls

Inline Functions (3) u u Researchers have developed compiler technology to inline function calls to functions in different C files This is part of a more general type of compiler optimization known as link-time optimization – Called link-time because the optimization is done after each C source file has been compiled, and the optimization is performed during linking – Both gcc and clang provide some simple link time optimizations – But link-time optimization is really poor in gcc, clang and all other production compilers I am aware of u Another problem is that some debuggers get confused when handling inline functions -- sometimes it is best to inline functions after debugging is finished.