class: center, middle ### COMP26020 Programming Languages and Paradigms Part 1: C Programming *** # The Preprocessor --- # The Preprocessor
-- .center[Actually things are a little bit more complicated 😛] --- # The Preprocessor
- Run by the compiler transparently - Performs **textual transformations** in the source code: -- 1. Include **headers** files (e.g. `stdio.h`) to access functions, data structures, and other constructs defined in other source files -- 2. Expand tokens named **macros** into more complex bits of code -- 3. **Conditionally enable/disable some bits of code** --- class: inverse, center, middle # Header Inclusion --- name:header # Header Inclusion - **Header** files: C code files with `.h` extension, contain what is necessary to use foreign libraries/source files - Should be **included** in the source file (`.c`) wishing to use the foreign code ```c // In the .c source file, include headers like that: #include
// Use <> to include a file from the default include path #include "my-custom-header.h" // Use "" for the local and user-supplied include directories ``` --- template: header
--- template: header - Headers can be inclued in several `.c` files composing a program - To avoid double definitions, **they should contain only declarations:** functions prototypes, variables/custom types/structs declaration, etc. - **No definitions** (i.e. function implementation or global variable affectation) --- class: inverse, center, middle # Macro Expansions --- # Macro Expansions - Textual substitutions, useful for compile-time defined constants - Generally indicated in capital letters as a convention -- .leftcol[ ```c #include
int main(int argc, char **argv) { int array[10]; // If I modify the array size, I shall not // forget to update the iteration bound // here too! for(int i=0; i<10; i++) { array[i] = i; printf("array[%d] = %d\n", i, array[i]); } return 0; } ``` ] -- .rightcol[ ```c #include
// If we want to change the array size, // we only need to update this: *#define ARRAY_SIZE 10 int main(int argc, char **argv) { * int array[ARRAY_SIZE]; * for(int i=0; i
`15-preprocessor/macro.c`
] ] --- # Macro Expansions - In general, **try to have a less hardcoded values as possible** - When you see that you are writing a constant value more then once, check if using a macro is possible! - Even if you have a constant used only once a macro can make it look more meaningful and easier to update in the future -- - Macros can also be used to write macro "functions" - Can be error-prone, see here: https://bit.ly/3EaQ2DA --- # Macro Expansions - Be careful with operator precedence! .leftcol[ ```c #define SIZE_1 10 #define SIZE_2 10 // We can use a macro in another macro's // definition: #define TOTAL SIZE_1 + SIZE_2 int main(int argc, char **argv) { // expect to print 10+10 = 20 * 2 = 40 printf("total twice = %d\n", TOTAL * 2); return 0; } ``` .codelink[
`15-preprocessor/macro-replacement-issue.c`
] ] --- # Macro Expansions - Be careful with operator precedence! Use parentheses! .leftcol[ ```c #define SIZE_1 10 #define SIZE_2 10 // We can use a macro in another macro's // definition: #define TOTAL SIZE_1 + SIZE_2 int main(int argc, char **argv) { // faulty! expands to: 10 + 10 * 2 printf("total twice = %d\n", TOTAL * 2); return 0; } ``` .codelink[
`15-preprocessor/macro-replacement-issue.c`
] ] .rightcol[ ```c #define SIZE_1 10 #define SIZE_2 10 // More than 1 macro in another macro's // definition? use parentheses *#define TOTAL (SIZE_1 + SIZE_2) int main(int argc, char **argv) { // correct, expands to: (10 + 10) * 2 printf("total twice = %d\n", TOTAL * 2); return 0; } ``` .codelink[
`15-preprocessor/macro-replacement-fix.c`
] ] ??? - One thing that can be confusing with macros is related to operator precedence - Let's have a look at the example on the left - We have two macros, size 1 that is 10 and size2 that is also 10 - We define a macro TOTAL that is the sum of both macros - Note that we can use a macro in another macro definition - So we expect TOTAL to be 20 - And then in the main function we multiply total by two so we expect the result to be 40 - Let's try it out - The thing is, because the preprocessor realised a textual transformation, we actually got this - With operator precedence, this evaluates to 30 - To get the correct behaviour, we need to put parentheses in the total macro, as follows - Overall, if your macro has more than a single item inside, just put parentheses to avoid issues --- class: middle, inverse, center # Conditional Compilation --- # Conditional Compilation ```c *#define DEBUG_MODE // controls the activation/deactivation of debug mode *#define VERBOSITY_LEVEL 4 // control the debug verbosity level #ifdef DEBUG_MODE int debug_function(); #endif int main(int argc, char **argv) { printf("hello, world\n"); #ifdef DEBUG_MODE debug_function(); #endif return 0; } #ifdef DEBUG_MODE int debug_function() { printf("This is printed only if the macro DEBUG_MODE is defined\n"); #if VERBOSITY_LEVEL > 3 printf("Additional debug because the verbosity level is high\n"); #endif /* VERBOSITY_LEVEL */ return 42; } #endif /* DEBUG_MODE */ ``` .codelink[
`15-preprocessor/conditional-compilation.c`
] ??? - One last thing I want to talk about is the conditional inclusion of code with preprocessor directives - Let's have a look at this example - We define two macros DEBUG_MODE and VERBOSITY_LEVEL --- # Conditional Compilation ```c *#define DEBUG_MODE // comment this to disable debug mode #define VERBOSITY_LEVEL 4 *#ifdef DEBUG_MODE *int debug_function(); *#endif int main(int argc, char **argv) { printf("hello, world\n"); *#ifdef DEBUG_MODE * debug_function(); *#endif return 0; } *#ifdef DEBUG_MODE *int debug_function() { * printf("This is printed only if the macro DEBUG_MODE is defined\n"); * *#if VERBOSITY_LEVEL > 3 * printf("Additional debug because the verbosity level is high\n"); *#endif /* VERBOSITY_LEVEL */ * * return 42; *} *#endif /* DEBUG_MODE */ ``` .codelink[
`15-preprocessor/conditional-compilation.c`
] ??? - Let's have first a look at DEBUG_MODE - It is defined without a particular value - Its goal is to control the inclusion or not of debug code - Through the use of #ifdef and #endif directives, all the code highlighted will be included in the compilation only if the DEBUG_MODE macro is defined - In other words, if we comment the first line, all this code won't be included in the resulting executable --- # Conditional Compilation ```c #define DEBUG_MODE *#define VERBOSITY_LEVEL 4 // update this to set the level of verbosity #ifdef DEBUG_MODE int debug_function(); #endif int main(int argc, char **argv) { printf("hello, world\n"); #ifdef DEBUG_MODE debug_function(); #endif return 0; } #ifdef DEBUG_MODE int debug_function() { printf("This is printed only if the macro DEBUG_MODE is defined\n"); *#if VERBOSITY_LEVEL > 3 // You can use the other C-like comparison operations (==, etc.) * printf("Additional debug because the verbosity level is high\n"); *#endif /* VERBOSITY_LEVEL */ return 42; } #endif /* DEBUG_MODE */ ``` .codelink[
`15-preprocessor/conditional-compilation.c`
] ??? - We have another macro, VERBOSITY_LEVEL - This one is defined with a value, 4 - With the #if directive we also include some code conditionally - We can use the superior or inferior operator, but also the equality with == and inequality with exclamation mark equal - Note that we use a comment to detail which #if this #endif corresponds, as preprocessor directives are generally not indented so can sometimes be confusing --- # Summary - Preprocessor: textual transformation before compilation - Automatically called by the compiler - Header inclusion, macro expansion, conditional code inclusion ---- .center[Feedback form: https://bit.ly/37s9JZ9]
??? - Let's recap - We saw the preprocessor, a textual transformation/substitution tool called automatically by the compiler during the build process of C and C++ programs - It allows to include header files - To expand macros - And to include some code conditionally - In the next video, we will talk about modular compilation, that is the construction of programs from several source files