diff --git a/base_4.md b/base_4.md index 0bef38e4aa447f3ae8b304a2ee0915f9ae3c225f..1c59509666c3073c9a6c5b6ecc13ba274a0b96e1 100644 --- a/base_4.md +++ b/base_4.md @@ -1,6 +1,6 @@ -% Base III +% Base IV % Inspirés des slides de F. Glück -% 2 octobre 2019 +% 7 octobre 2020 # Les tableaux (1/6) @@ -168,461 +168,190 @@ int main(void) { <!-- que retourne sizeof(tab[]) --> -# Représentation des variables en mémoire (1/N) +# Introduction à `make` -## La mémoire +## A quoi ça sert? -* La mémoire est: - - ... un ensemble de bits, - - ... accessible via des adresses, +- Automatiser le processus de conversion d'un type de fichier à un autre, en *gérant les dépendances*. +- Effectue la conversion des fichiers qui ont changé uniquement. +- Utilisé pour la compilation: + - Création du code objet à partir des sources. + - Création de l'exécutable à partir du code objet. +- Tout "gros" projet utilise `make` (pas uniquement en `C`). - +------+----------+----------+------+----------+------+------+ - | bits | 00110101 | 10010000 | .... | 00110011 | .... | .... | - +======+==========+==========+======+==========+======+======+ - | addr | 2000 | 2001 | .... | 4000 | .... | .... | - +------+----------+----------+------+----------+------+------+ +# Utilisation de `make` - - ... gérée par le système d'exploitation. - - ... séparée en deux parties: **la pile** et **le tas**. +Le programme `make` exécutera la série d'instruction se trouvant dans un `Makefile` (ou `makefile` ou `GNUmakefile`). -## Une variable +## Le `Makefile` -* Une variable, `type a = valeur`{.C}, possède: - - un type (`char`{.C}, `int`{.C}, ...), - - un contenu (une séquence de bits qui encode `valeur`{.C}), - - une adresse mémoire (accessible via `&a`{.C}), - - une portée. +- Contient une liste de *règles* et *dépendances*. +- Règles et dépendances construisent des *cibles*. +- Ici utilisé pour compiler une série de fichiers sources - -# Représentation des variables en mémoire (2/N) - -{#fig:memory width=100%} - -# Les pointeurs (1/N) - -- Un pointeur est une adresse mémoire. - - ```C - type *id; - ``` -- Pour interpréter le contenu de ce qu'il pointe, il doit être typé. -- Un pointeur n'est rien d'autre qu'un entier (64bit sur x86-64, soit 8 octets). -- Un pointeur peut être **déréférencé**: on accède à la valeur située à l'adresse mémoire sur laquelle il pointe. - - ```C - char *c; // déclaration pointeur de char - *c = 'a'; // assign. 'a' à valeur pointée par c - c = 1000; // on modifie l'adresse pointée par c - char d = *c; // on lit la valeur pointée par c. UB! - ``` - -- `NULL`{.C} (ou `0`{.C}) est la seule adresse **toujours** invalide. - -# Les pointeurs (2/N) - -{#fig:memory width=100%} - -# Les pointeurs (3/N) - -- Permettent d'accéder à une valeur avec une indirection. - - ```C - int a = 2; - int *b = &a; - *b = 7; // on met 7 dans la case pointée par b - // ici a == 7 aussi - a = -2; // ici *b == -2 aussi - ``` - -- Permettent d'avoir plusieurs chemins d'accès à une valeur. -- Lire **et** écrire en même temps dans un bout de mémoire devient possible: **danger**. - -# Quiz: Les pointeurs - -## [Quiz: Les pointeurs](https://cyberlearn.hes-so.ch/mod/evoting/view.php?id=1038526) - -# La fonction `sizeof()` (1/N) - -- La fonction `sizeof()`{.C} permet de connaître la taille en octets: - - d'une valeur, - - d'un type, - - d'une variable. -- Soit `int a = 2`{.C}, sur l'architecture x86_64 que vaut: - - `sizeof(a)`{.C}? - - `sizeof(&a)`{.C}? -- Soit `char b = 2`{.C}, - - `sizeof(b)`{.C}? - - `sizeof(&b)`{.C}? - -# La fonction `sizeof()` (2/N) - -- Réponses: - - `sizeof(a) == 4`{.C}, `int`{.C} entier 32 bits. - - `sizeof(&a) == 8`{.C}, une adresse est de 64 bits. - - `sizeof(b) == 1`{.C}, `char`{.C} entier 8 bits. - - `sizeof(&b) == 8`{.C}, une adresse est de 64 bits. - -# Types complexes: `struct`{.C} (1/N) - -## Généralités - -- Plusieurs variables qu'on aimerait regrouper dans un seul type: `struct`{.C}. - - ```C - struct complex { // déclaration - double re; - double im; - }; - - struct complex num; // déclaration de num - ``` -- Les champs sont accessible avec le sélecteur "`.`{.C}". - - ```C - num.re = 1.0; - num.im = -2.0; - ``` - -# Types complexes: `struct`{.C} (2/N) - -## Simplifications - -- `typedef`{.C} permet de définir un nouveau type. - - ```C - typedef unsinged int uint; - typedef struct complex complex_t; - typedef struct complex { - double re, im; - } complex_t; - ``` -- L'initialisation peut aussi se faire avec - - ```C - complex_t num = {1.0, -2.0}; // re = 1.0, im = -2.0 - complex_t num = {.im = 1.0, .re = -2.0}; - complex_t num = {.im = 1.0}; // argl! .re non initialisé - complex_t num2 = num; // copie - ``` - -# Types complexes: `struct`{.C} (3/N) - -## Pointeurs - -- Comme pour tout type, on peut avoir des pointeurs vers un `struct`{.C}. -- Les champs sont accessible avec le sélecteur `->`{.C} - - ```C - complex_t *num; // on crée un pointeur - num->re = 1.0; // seg fault... - num->im = -1.0; // mémoire pas allouée. - ``` - -{#fig:compilation width=100%} - - -# Allocation dynamique de mémoire (1/N) - -- La fonction `malloc`{.C} permet d'allouer dynamiquement (pendant l'exécution du programme) une zone de mémoire contiguë. - - ```C - #include <stdlib.h> - void *malloc(size_t size); - ``` -- `size`{.C} est la taille de la zone mémoire **en octets**. -- Retourne un pointeur sur la zone mémoire ou `NULL`{.C} en cas d'échec: **toujours vérifier** que la valeur retournée est `!= NULL`{.C}. - -# Allocation dynamique de mémoire (2/N) - -- Avec l'exemple de tout à l'heure: - - ```C - complex_t *num = malloc(sizeof(complex_t)); - num->re = 1.0; // maintenant ... - num->im = -1.0; // ça marche. - ``` -- La zone mémoire **n'est pas** initialisée. -- La mémoire doit être désallouée explicitement $\Rightarrow$ **fuites mémoires**. -<!-- - Toujours garder un pointeur sur la mémoire allouée sinon **pointeur pendouillant**. --> - -{#fig:compilation width=100%} - -# Allocation dynamique de mémoire (3/N) - -- La fonction `free()`{.C} permet de libérer une zone préalablement allouée avec `malloc()`{.C}. - - ```C - #include <stdlib.h> - void free(void *ptr); - ``` -- Pour chaque `malloc()`{.C} doit correspondre exactement un `free()`{.C}. -- Si la mémoire n'est pas libérée: **fuite mémoire** (l'ordinateur plante quand il y a plus de mémoire). -- Si la mémoire est **libérée deux** fois: seg fault. -- Pour éviter les mauvaises surprises mettre `ptr`{.C} à `NULL`{.C}. - -# Allocation dynamique de mémoire (4/N) - -## Tableaux dynamiques - -- Pour allouer un espace mémoire de 50 entiers: - - ```C - int *p = malloc(50 * sizeof(int)); - ``` -- Cette espace peut alors être utilisé comme un tableau de 50 entiers: - - ```C - for (int i = 0; i < 50; ++i) { - p[i] = 0; - } ``` - -## Arithmétique de pointeurs - -- On peut parcourir la mémoire différemment qu'avec l'indexation - - ```C - int *p = malloc(50 * sizeof(int)); - // initialize somehow - double a = p[7]; - double b = *(p + 7); // on avance de 7 "double" - p[0] == *p; // le pointeur est le premier élément + $ gcc -c example.c # + plein d'options.. + $ gcc -o example exemple.o # + plein d'options ``` -# Allocation dynamique de mémoire (5/N) - -## Arithmétique de pointeurs +:::::::::::::: {.columns} -{#fig:compilation width=100%} +::: {.column width="55%"} -# Allocation dynamique de mémoire (6/N) +## `Makefile` -## Pointeur de pointeur +```bash +example: example.o + gcc -o example example.o -- Tout comme une valeur a une adresse, un pointeur a lui-même une adresse: +exmaple.o: exmaple.c example.h + gcc -c example.c +``` +::: +::: {.column width="45%"} - ```C - int a = 2; - int *b = &a; - int **c = &b; - ``` -- Chaque `*`{.C} ou `&`{.C} rajoute une indirection. +## Terminal -# Allocation dynamique de mémoire (7/N) +```bash +$ make +gcc -c example.c +gcc -o example example.o +``` +::: +:::::::::::::: -## Pointeur de pointeur +# Syntaxe d'un `Makefile` (1/4) +{#fig:ex_makefile width=100%} -{#fig:compilation height=100%} +# Syntaxe d'un `Makefile` (2/4) -# Allocation dynamique de mémoire (8/N) +{#fig:ex_makefile_cible width=100%} -- Avec `malloc()`, on peut allouer dynamiquement des tableaux de pointeurs: +# Syntaxe d'un `Makefile` (3/4) - ```C - int **p = malloc(50 * sizeof(int*)); - for (int i = 0; i < 50; ++i) { - p[i] = malloc(70 * sizeof(int)); - } - int a = p[5][8]; // on indexe dans chaque dimension - ``` +{#fig:ex_makefile_dep width=100%} -- Ceci est une matrice (un tableau de tableau). +# Syntaxe d'un `Makefile` (4/4) -# Prototypes de fonctions (1/N) +{#fig:ex_makefile_regle width=100%} -## Principes généraux de programmation +# Principe de fonctionnement -- Beaucoup de fonctionnalités dans un code $\Rightarrow$ Modularisation. -- Modularisation du code $\Rightarrow$ écriture de fonctions. -- Beaucoup de fonctions $\Rightarrow$ regrouper les fonctions dans des fichiers séparés. +1. `make` cherche le fichier `Makefile`, `makefile` ou `GNUmakefile` dans le répertoire courant. +2. Par défaut exécute la première cible, ou celle donnée en argument. +3. Décide si une cible doit être régénérée en comparant la date de modification (on recompile que ce qui a été modifié). +4. Regarde si les dépendances doivent être régénérées: + - Oui: prend la première dépendance comme cible et recommence à 3. + - Non: exécute la règle. -## Mais pourquoi? +`make` a un comportement **récursif**. -- Lisibilité. -- Raisonnement sur le code. -- Débogage. +# Exemple avancé -## Exemple +:::::::::::::: {.columns} -- Libraire `stdio.h`: `printf()`{.C}, `scanf()`{.C}, ... +::: {.column width="55%"} -# Prototypes de fonctions (2/N) +## `Makefile` -- Prototypes de fonctions nécessaires quand: +```bash +hello: hello.o main.o + gcc hello.o main.o -o hello - 1. Utilisation de fonctions dans des fichiers séparés. - 2. Utilisation de librairies. -- Un prototype indique au compilateur la signature d'une fonction. -- On met les prototypes des fonctions **publiques** dans des fichiers *headers*, extension `.h`. -- Les *implémentations* des fonctions vont dans des fichier `.c`. +hello.o: hello.c hello.h + gcc -Wall -Wextra -c hello.c -# Prototypes de fonctions (3/N) +main.o: main.c + gcc -Wall -Wextra -c main.c -## Fichier header +clean: + rm -f *.o hello -- Porte l'extension `.h` -- Contient: - - définitions des types - - prototypes de fonctions - - macros - - directives préprocesseur (cf. plus loin) -- Utilisé pour décrire **l'interface** d'une librairie ou d'un module. -- Un fichier `C` (extension `.c`) utilise un header en *l'important* avec la directive `#include`{.C}: +rebuild: clean hello +``` +::: +::: {.column width="45%"} - ```C - #include <stdio.h> // libraire dans LD_LIBRARY_PATH - #include "chemin/du/prototypes.h"// chemin explicite - ``` +## Un graph complexe -# Génération d'un exécutable (1/N) +{#fig:complex_makefile width=100%} -## Un seul fichier source +::: +:::::::::::::: -{#fig:compilation width=100%} +# Factorisation -# Génération d'un exécutable (2/N) +:::::::::::::: {.columns} -## Un seul fichier source +::: {.column width="55%"} +## Ancien `Makefile` ```bash -gcc proc.c -o prog -``` - -1. **Précompilation: ** `gcc` appelle `cpp`, le préprocesseur qui effectue de la substitution de texte (`#define`, `#include`, macros, ...) et génère le code `C` à compiler, portant l'extension `.i` (`prog.i`). -2. **Compilation assembleur: ** `gcc` compile le code C en code assembleur, portant l'extension `.s` (`prog.s`). -3. **Compilation code objet: ** `gcc` appelle `as`, l'assembleur, qui compile le code assembleur en code machine (code objet) portant l'extension `.o` (`prog.o`). -4. **Édition des liens: ** `gcc` appelle `ld`, l'éditeur de liens, qui lie le code objet avec les librairies et d'autres codes objet pour produire l'exécutable final (`prog`). - -Les différents codes intermédiaires sont effacés. - -# Génération d'un exécutable (3/N) - -## Plusieurs fichiers sources +hello: hello.o main.o + gcc hello.o main.o -o hello -{#fig:compilation_plusieurs width=100%} +hello.o: hello.c hello.h + gcc -Wall -Wextra -c hello.c -# Génération d'un exécutable (4/N) +main.o: main.c + gcc -Wall -Wextra -c main.c -::: Main +clean: + rm -f *.o hello -## `main.c` - -```C -#include <stdio.h> -#include "sum.h" -int main() { - int tab[] = {1, 2, 3, 4}; - printf("sum: %d\n", sum(tab, 4)); - return 0; -} +rebuild: clean hello ``` ::: - -:::::::::::::: {.columns} - ::: {.column width="45%"} -## `sum.h` +## Nouveau `Makefile` -```C -#ifndef _SUM_H_ -#define _SUM_H_ +```bash +CC=gcc -Wall -Wextra -int sum(int tab[], int n); +hello: hello.o main.o + $(CC) $^ -o $@ -#endif -``` -::: -::: {.column width="55%"} +hello.o: hello.c hello.h + $(CC) -c $< -## `sum.c` +main.o: main.c + $(CC) -c $< -```C -#include "sum.h" -int sum(int tab[], int n) { - int s = 0; - for (int i = 0; i < n; i++) { - s += tab[i]; - } - return s; -} +clean: + rm -f *.o hello + +rebuild: clean hello ``` -::: +::: :::::::::::::: +# Variables -# Génération d'un exécutable (4/N) - -La compilation séparée se fait en plusieurs étapes. - -## Compilation séparée - -1. Générer séparément les fichiers `.o` avec l'option `-c`. -2. Éditer les liens avec l'option `-o` pour générer l'exécutable. - -## Exemple - -- Création des fichiers objets, `main.o` et `sum.o` +## Variables utilisateur +- Déclaration + ```bash - $ gcc -Wall -Wextra -std=c11 -c main.c - $ gcc -Wall -Wextra -std=c11 -c sum.c + id = valeur + id = valeur1 valeur2 valeur3 ``` -- Édition des liens - +- Utilisation + ```bash - $ gcc main.o sum.o -o prog - ``` - -# Préprocesseur (1/N) - -## Généralités - -- Première étape de la chaîne de compilation. -- Géré automatiquement par `gcc` ou `clang`. -- Lit et interprète certaines directives: - 1. Les commentaires (`//`{.C} et `/* ... */`{.C}). - 2. Les commandes commençant par `#`{.C}. -- Le préprocesseur ne compile rien, mais subtitue uniquement du texte. - -## La directive `define`{.C} - -- Permet de définir un symbole: - - ```C - #define PI 3.14159 - #define _SUM_H_ - ``` -- Permet de définir une macro. - - ```C - #define NOM_MACRO(arg1, arg2, ...) [code] - ``` - -# Préprocesseur (2/N) - -## La directive `include`{.C} - -- Permet d'inclure un fichier. -- Le contenu du fichier est ajouté à l'endroit du `#include`{.C}. -- Inclusion de fichiers "globaux" ou "locaux" - - ```C - #include <file.h> // LD_LIBRARY_PATH - #include "other_file.h" // local path + $(id) ``` -- Les inclusions multiples peuvent poser problème: définitions multiples. Les headers commencent par: +- Déclaration à la ligne de commande - ```C - #ifndef _VAR_ - #define _VAR_ - /* - commentaires - */ - #endif + ```bash + make CFLAGS="-O3 -Wall" ``` +## Variables internes - +- `$@` : la cible +- `$^` : la liste des dépendances +- `$<` : la première dépendance +- `$*` : le nom de la cible sans extension