diff --git a/base_4.md b/base_4.md new file mode 100644 index 0000000000000000000000000000000000000000..0bef38e4aa447f3ae8b304a2ee0915f9ae3c225f --- /dev/null +++ b/base_4.md @@ -0,0 +1,628 @@ +% Base III +% Inspirés des slides de F. Glück +% 2 octobre 2019 + +# Les tableaux (1/6) + +## Généralités + +- `C` offre uniquement des tableaux statiques + - Un tableau est un "bloc" de mémoire contiguë associé à un nom + - taille fixe déterminée à la déclaration du tableau + - la taille ne peut pas être changée. + - Pas d’assignation de tableaux. + - Un tableau déclaré dans une fonction ou un bloc est détruit à la sortie de celle/celui-ci + - $\Rightarrow$ Un tableau local à une fonction ne doit **jamais être retourné** (aussi valable pour toute variable locale)! +- Les éléments d’un tableau sont accédés avec `[i]`{.C} où `i`{.C} est l’index de l’élément. +- Le premier élément du tableau à l’index `0`{.C}! +- Lorsqu’un tableau est déclaré, la taille de celui-ci doit toujours être spécifiée, sauf s’il est initialisé lors de sa déclaration. + +# Les tableaux (2/6) + +## Exemple + +```C +float tab1[5]; // tableau de floats à 5 éléments + // ses valeurs sont indéfinies + +int tab2[] = {1, 2, 3}; // tableau de 3 entiers, + // taille inférée + +int val = tab2[1]; // val vaut 2 à présent + +int w = tab1[5]; // index hors des limites du tableau + // comportement indéfini! + // pas d'erreur du compilateur +``` + +<!-- TODO QUIZ: + +```C +int a1[5]; // OK +int a2[] = { 1, 2, 3 }; // OK +int a3[4][5]; // OK +int [] a4; // Erreur +int a5[]; // Erreur + +int[] function(void) { // Erreur + int array[5]; // OK + return array; // Erreur +} + +void foo(int a[]) { // OK + a[3] = 0; // OK +} + +void bar(void) { + int a[5]; // OK + foo(a); // OK + a = a5; // Erreur +} +``` --> + +<!-- ```C +#include <stdio.h> + +int main(void) { + char i; + char a1[] = { 100,200,300,400,500 }; + char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + a2[10] = 42; + + for (i = 0; i < 5; i++) { + printf("a1[%d] = %d\n", i, a1[i]); + } + + return 0; +} +``` --> +# Les tableaux (3/6) + +## Itérer sur les éléments d'un tableau + +```C +int x[10]; +for (int i = 0; i < 10; ++i) { + x[i] = 0; +} +int j = 0; +while (j < 10) { + x[j] = 1; + j += 1; +} +int j = 0; +do { + x[j] = -1; + j += 1; +} while (j < 9) +``` + + +# Les tableaux (4/6) + +## Les tableaux comme argument + +- Un tableau est le pointeur vers sa première case. +- Pas moyen de connaître sa taille: `sizeof()`{.C} inutile. +- Toujours spécifier la taille d'un tableau passé en argument. + + ```C + void foo(int tab[]) { // sans taille... + for (int i = 0; i < ?; ++i) { + // on sait pas quoi mettre pour ? + printf("tab[%d] = %d\n", i, tab[i]); + } + } + // n doit venir avant tab, [n] optionnel + void bar(int n, int tab[n]) { + for (int i = 0; i < n; ++i) { + printf("tab[%d] = %d\n", i, tab[i]); + } + } + ``` + +# Les tableaux (5/6) + +## Quels sont les bugs dans ce code? + +```C +#include <stdio.h> + +int main(void) { + char i; + char a1[] = { 100,200,300,400,500 }; + char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + a2[10] = 42; + + for (i = 0; i < 5; i++) { + printf("a1[%d] = %d\n", i, a1[i]); + } + + return 0; +} +``` + +# Les tableaux (6/6) + +## Quels sont les bugs dans ce code? + +```C +#include <stdio.h> + +int main(void) { + char i; + // 200, .., 500 char overflow + char a1[] = { 100,200,300,400,500 }; + char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + a2[10] = 42; // [10] out of bounds + + for (i = 0; i < 5; i++) { + printf("a1[%d] = %d\n", i, a1[i]); + } + + return 0; +} +``` + +<!-- TODO quiz: --> + +<!-- que retourne sizeof(tab[]) --> + +# Représentation des variables en mémoire (1/N) + +## La mémoire + +* La mémoire est: + - ... un ensemble de bits, + - ... accessible via des adresses, + + +------+----------+----------+------+----------+------+------+ + | bits | 00110101 | 10010000 | .... | 00110011 | .... | .... | + +======+==========+==========+======+==========+======+======+ + | addr | 2000 | 2001 | .... | 4000 | .... | .... | + +------+----------+----------+------+----------+------+------+ + + - ... gérée par le système d'exploitation. + - ... séparée en deux parties: **la pile** et **le tas**. + +## Une variable + +* 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. + + +# 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 + ``` + +# Allocation dynamique de mémoire (5/N) + +## Arithmétique de pointeurs + +{#fig:compilation width=100%} + +# Allocation dynamique de mémoire (6/N) + +## Pointeur de pointeur + +- Tout comme une valeur a une adresse, un pointeur a lui-même une adresse: + + ```C + int a = 2; + int *b = &a; + int **c = &b; + ``` +- Chaque `*`{.C} ou `&`{.C} rajoute une indirection. + +# Allocation dynamique de mémoire (7/N) + +## Pointeur de pointeur + + +{#fig:compilation height=100%} + +# Allocation dynamique de mémoire (8/N) + +- Avec `malloc()`, on peut allouer dynamiquement des tableaux de pointeurs: + + ```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 + ``` + +- Ceci est une matrice (un tableau de tableau). + +# Prototypes de fonctions (1/N) + +## Principes généraux de programmation + +- 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. + +## Mais pourquoi? + +- Lisibilité. +- Raisonnement sur le code. +- Débogage. + +## Exemple + +- Libraire `stdio.h`: `printf()`{.C}, `scanf()`{.C}, ... + +# Prototypes de fonctions (2/N) + +- Prototypes de fonctions nécessaires quand: + + 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`. + +# Prototypes de fonctions (3/N) + +## Fichier header + +- 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}: + + ```C + #include <stdio.h> // libraire dans LD_LIBRARY_PATH + #include "chemin/du/prototypes.h"// chemin explicite + ``` + +# Génération d'un exécutable (1/N) + +## Un seul fichier source + +{#fig:compilation width=100%} + +# Génération d'un exécutable (2/N) + +## Un seul fichier source + +```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 + +{#fig:compilation_plusieurs width=100%} + +# Génération d'un exécutable (4/N) + +::: Main + +## `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; +} +``` +::: + +:::::::::::::: {.columns} + +::: {.column width="45%"} + +## `sum.h` + +```C +#ifndef _SUM_H_ +#define _SUM_H_ + +int sum(int tab[], int n); + +#endif +``` +::: +::: {.column width="55%"} + +## `sum.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; +} +``` +::: + +:::::::::::::: + + +# 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` + + ```bash + $ gcc -Wall -Wextra -std=c11 -c main.c + $ gcc -Wall -Wextra -std=c11 -c sum.c + ``` +- Édition des liens + + ```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 + ``` +- Les inclusions multiples peuvent poser problème: définitions multiples. Les headers commencent par: + + ```C + #ifndef _VAR_ + #define _VAR_ + /* + commentaires + */ + #endif + ``` + + +