class: center, middle ### COMP26020 Programming Languages and Paradigms Part 1: C Programming *** # Modular Compilation --- # Modular Compilation - Large programs: need to organise the code in several source files - [Redis's `src` folder](https://github.com/redis/redis/tree/7.0.0/src) - [Linux](https://github.com/torvalds/linux/tree/v6.4) kernel sources (59000 C source files for v6.11!) - **How to break up the program code into different files representing well-isolated compilation units**? - Modular compilation, covered in this slide deck -- - Also, can't run `gcc <59K source files>` each time we do a small update to one or a few source files - **How to automate efficiently the build process?** - Automated compilation, covered in the next slide deck --- class: inverse, middle, center # A Closer Look at the Compilation Phase --- name: phase # A Closer Look at the Compilation Phase
-- .center[Actually things are still a little bit more complicated 😛] --- # A Closer Look at the Compilation Phase
--- # A Closer Look at the Compilation Phase
--- class: inverse, middle, center # Breaking Down the Program into Modules --- # Breaking Down the Program into Modules - Consider a hypothetical server application working as follows:
--- name: breaking # Breaking Down the Program into Modules - Server implemented into 3 `.c` source files, also named **modules**: --- template: breaking
--- template: breaking
```bash gcc -c main.c -o main.o # build main.o gcc -c parser.c -o parser.o # build parser.o gcc -c network.c -o network.o # build network.o gcc main.o network.o parser.o -o prog # link prog ``` --- name: interractions # Breaking Down the Program into Modules - Each module (`.c` source file) exposes an **interface** callable from the other source files - Should be **as small as possible** to hide internal code/data --- template: interractions .leftlargecol[ - Assume the following interactions: - Server starts, `main.c` calls `init_network()` implemented in `network.c` to init. networking - `main.c` then runs the main sever loop: - Calls `rcv_request()` implemented in `network.c` to wait for the next request - When received, calls `parse_req()` implemented by `parser.c` to process the request - Rince and repeat ] .rightsmallcal[
] --- # Header File for Modular Compilation - There is generally 1 header file per module, **defining its interface**. - Can be included in several `.c` files so **must contain only declarations** - Function prototypes, struct/typedefs/enums declarations, variable declarations - **No definitions** (i.e. function body/global variable initialisation) - For example `network.h`: .leftcol[ ```c #ifndef NETWORK_H // include guard #define NETWORK_H typedef struct { int id; char content[128]; } request; void init_network(); int rcv_request(request *r); #endif /* NETWORK_H */ // ``` .codelink[
`16-modular-compilation/network.h`
] ] .rightcol[
] ??? - So we create generally one header file per module exporting an interface - We have the architecture of our program on the left, we'll have 1 header for network and 1 header for parser - In addition to the 3 source files, network.c, parser.c and main.c - These header files will be included in several source files so it is important that they contain only declarations - In other words, function prototypes, struct/typedefs/enums declarations, variable declarations - They should contain no definitions - We see for example `network.h` on the slide - It contains everything that the world outside the module network needs to know in order to use the network interface - So, the prototypes of the two function of the interface - But also the declaration of the struct that is used as parameter in 1 of these functions - Note the enclosing #ifdef NETWORK_H, it's call an include guard - It avoid the problem of double declaration when we include in a C file several files that themselves include this header --- # Breaking Down the Program into Modules - `network.c`: .leftcol[ ```c /* std includes here */ *#include "network.h" // this function and variable are internal // so they are not declared in network.h // the keyword static force their use // to be only within the network.c file static void generate_request(request *r); static int request_counter = 0; void init_network() { /* init code here ... */ } int rcv_request(request *r) { generate_request(r); /* ... */ } static void generate_request(request *r) { /* ... */ } ``` .codelink[
`16-modular-compilation/network.c`
] ] .rightcol[
] ??? - Now if we look at the implementation of the module network, that is the content of network.c, it looks like that - We need to include the corresponding header to get access to the struct definition - We have a function and a global variable that are internal to the module and they are not supposed to be called from outside, we can enforce that with the static keyword - And we have the implementation of the 2 functions that are exported by the network module --- # Breaking Down the Program into Modules .leftcol[ - `parser.h`: ```c #ifndef PARSER_H #define PARSER_H /* needed for the definition of request: */ #include "network.h" void parse_req(request *r); #endif ``` .codelink[
`16-modular-compilation/parser.h`
]
] .rightcol[ - `parser.c`: ```c #include
*#include "parser.h" static void internal1(request *r); static void internal2(request *r); void parse_req(request *r) { internal1(r); internal2(r); } static void internal1(request *r) { /* ... */ } static void internal2(request *r) { /* ... */ } ``` .codelink[
`16-modular-compilation/parser.c`
] ] ??? - If we look at parser.h and parser.c, it follows the same principles as for the the network module - We have an external interface declared in the header - We also need the declaration of the struct so we include network.h in the parser header - And in the module implementation we include the parser header - We have a couple of internal functions, as well as the exported function --- # Breaking Down the Program into Modules .leftcol[ - `main.c`: ```c *#include "network.h" *#include "parser.h" int main(int argc, char **argv) { request req; /* call functions from network module */ init_network(); rcv_request(&req); /* call function from parser module */ parse_req(&req); /* ... */ } ``` .codelink[
`16-modular-compilation/main.c`
] ] .rightcol[
] ??? - Finally, main.c looks like that - It's including both header to get access to the interface function prototypes - And within the main function the functions from the interface can be called - We can compile our program like this --- # Compile & Test ```sh # Compile the .c source files, in no particular order: $ gcc -c main.c -o main.o $ gcc -c parser.c -o parser.o $ gcc -c network.c -o network.o # Link the final executable: $ gcc main.o parser.o network.o -o prog # Launch it $ ./prog ``` --- # Summary - **Modular Compilation:** breaking down a program's sources into multiple header `.h` files and source `.c` files - Proper source organisation is important for medium/large programs - Export using header files only the interface supposed to be used from outside the module ---- .center[Feedback form: https://bit.ly/3lInZ8h]
??? - Let's recap - Breaking down C/C++ code into several files - It is done with a combination of header files, containing only definitions and included where needed - and source files, containing implementation, in other words definitions - In the next video, we will see how to automate the compilation of program made of multiple sources