--- title: Base V date: 14 octobre 2020 --- # Rappel: représentation des variables en mémoire {width=100%} # Rappel: Les pointeurs (1/3) - 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. # Rappel: Les pointeurs (2/3) {width=100%} # Rappel: Les pointeurs (3/3) - 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**. # Pointeurs et `const` ## Deux niveaux de constance - Le mot clé `const` permet de déclarer des valeurs "constantes" qui ne changeront plus en cours d'exécution du programme. - Mais qu'est-ce que cela veut dire pour les pointeurs? ```C int n = 12; const int *p = &n; // la valeur *p est const, p non int const *p = &n; // la valeur *p est const, p non int *const p = &n; // la valeur p est const, *p non const int *const p = &n; // la valeur p et *p sont const ``` ## Exemples ```C int n = 12; int m = 13; const int *p = &n; // la valeur *p est const, p non *p = m; // erreur de compilation. p = &m; // OK int const *p = &n; // la valeur *p est const, p non *p = m; // erreur de compilation. p = &m; // OK int *const p = &n; // la valeur p est const, *p non *p = m; // OK p = &m; // erreur de compilation. const int *const p = &n; // la valeur p et *p sont const *p = m; // erreur de compilation. p = &m; // erreur de compilation. ``` # La fonction `sizeof()` (1/2) - 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/2) - 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. # Allocation dynamique de mémoire (1/8) - 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/8) - On peut allouer un `complex_t`{.C}: ```C complex_t *num = malloc(sizeof(complex_t)); num->re = 1.0; num->im = -1.0; ``` - 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**. --> {width=100%} # Allocation dynamique de mémoire (3/8) - 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/8) ## 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/8) ## Arithmétique de pointeurs {width=100%} # Allocation dynamique de mémoire (6/8) ## 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/8) ## Pointeur de pointeur {height=100%} # Allocation dynamique de mémoire (8/8) - 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). # Les *sanitizers* Il existe différents outils pour détecter les problèmes mémoire: * Dépassement de capacité de tableaux. * Utilisation de mémoire non allouée. * Fuites mémoire. * ... Notamment: * Valgrind. * Sanitizers. Ici on utilise les sanitizers (modification de la ligne de compilation): ```bash gcc -o main main.c -g -fsanitize=address -fsanitize=leak ``` **Attention:** Il faut également faire l'édition des liens avec les sanitizers.