class: center, middle ### COMP26020 Programming Languages and Paradigms Part 1: C Programming *** # The Preprocessor --- # The Preprocessor
-- .center[Actually, things are a little more complicated...] --- # The Preprocessor
- Run by the compiler transparently - Performs **textual transformations** in the source code: -- 1. Include **header** files (e.g., `stdio.h`) to use code defined elsewhere -- 2. Expand **macros**: identifiers replaced with other pieces of code -- 3. **Conditionally enable/disable selected pieces of code** --- class: inverse, center, middle # Header Inclusion --- name:header # Header Inclusion - **Header** file: C code file, by convention with a `.h` extension, containing what is necessary to use other libraries/source files -- - Meant to be **included** in the source files (`.c`) that use the code in question -- ```c // In a .c source file, two methods to include headers: #include
// <>: search the system include paths #include "my-custom-header.h" // "": search the including file's directory first, // then the system include paths ``` --- template: header
--- # Header Inclusion - Headers can be included in several `.c` files composing a program - To avoid double definitions, **headers should contain only declarations:** - Function prototypes - Global variable declarations (with `extern`), custom types, struct definitions - Macros and other preprocessor directives - **No definitions**, i.e., no function bodies or global variable definitions (initialisations) --- class: inverse, center, middle # Macro Expansions --- # Macro Expansions - Textual substitutions, useful for example for constants defined at compile time - By convention, macro names are written in capital letters -- .leftcol[ ```c #include
int main(int argc, char **argv) { int array[10]; // If I modify the array size, I must 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 as few hard-coded values as possible** - When you find yourself writing a constant value more than once, consider using a macro! - Even for a constant used only once, a macro can make it more meaningful and easier to update in the future -- - Macros can also take parameters, i.e., function-like macros - Can be error-prone, see here: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/cpp/Macro-Pitfalls.html --- # Macro Expansions - Be careful with operator precedence! .leftcol[ ```c #include
#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) { // should be 10+10 = 20 * 2 = 40, right? 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 #include
#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 #include
#define SIZE_1 10 #define SIZE_2 10 // More than 1 element 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 SIZE_2 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 performs a textual substitution, 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 contains more than a single item, just put parentheses around it 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 // controls 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 out 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 first have 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 the #ifdef and #endif directives, all the highlighted code will be included in the compilation only if the DEBUG_MODE macro is defined - In other words, if we comment out the first line, none of this code will 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 // Other C comparison operators (==, !=, etc.) also work * 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 greater-than or less-than operators, but also equality with == and inequality with != - Note that we use a comment to indicate which #if this #endif corresponds to: preprocessor directives are generally not indented, which 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 us 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