Allocation dynamique
PRÉREQUIS
- Cours n°2 - Chaîne de caractères
- Cours n°7 - Adresses mémoires et pointeurs
- Cours n°8 - Fonctions et prototypes
PETITE INTRODUCTION
Allocation dynamique ? Qu'est-ce que ce mot barbare ? Limite ça ferait peur...
Eh bien je dirais que vous avez raison d'avoir peur, non pas pour le terme allocation dynamique, mais pour ce que cela peut engendrer. Vous allez jouer avec la mémoire de votre ordinateur et ce n'est jamais quelque chose d'anodin... Il faut être strict et rigoureux dans votre code. Ne vous inquiétez pas, pour cela vous aurez à votre disposition des outils permettant de contrôler si des erreurs mémoire sont faîtes.
Pourquoi utiliser l'allocation dynamique ?
Allocation veut dire allouer, et allouer quoi ? De la mémoire !
Lorsque vous ne connaissez pas par avance quelle mémoire vous avez besoin, l'allocation dynamique rentre en jeu et permet d'optimiser et alléger votre RAM. Attention, allouer a un coût sur le temps d'exécution, trop d'allocation peut être non bénéfique par rapport à une allocation statique.
ALLOCATION STATIQUE ?
Voici un exemple
Code:
char tab[128];
ALLOCATION DYNAMIQUE ?
Gérer d'une façon optimum peut comporter des risques de fuites mémoire, qui engendre une réduction inutile de la mémoire, une impossibilité de réutiliser cette mémoire non libérée et des contre performances de votre programme.
2 outils pour faire cela :
- malloc - Alloue de la mémoire
- free - Libère la mémoire allouée
Que dit la documentation ?
Code:
void *malloc(size_t size);
Le type void est un type générique, en tant que pointeur, c'est retourné une adresse d'un type quelconque.
Code:
void free(void *ptr);
PROBLÉMATIQUE
Vous l'attendiez, là voilà ! On vous demande de faire une copie d'une chaîne de caractères dont vous ne connaissez absolument pas sa taille ! En clair on demande à l'utilisateur une chaîne de caractère et on vous demande d'en faire une copie en limitant au maximum l'utilisation mémoire.
Statiquement, on pourrait faire
Code:
#include <stdio.h> #include <string.h> /* pour strlen */ #define MAX_LENGTH 256 /* 256 caractères maximum, '\0' compris */ int copy(char result[MAX_LENGTH], const char *array); int main(void){ char res[MAX_LENGTH]; /* déclaration d'un tableau de 256 caractères */ const char *test = "Bonjour"; /* déclaration d'un tableau de caractères à copier */ int value; value = copy(res, test); /* copie de test dans res */ if (value == 0) puts(res); /* Affichage de res */ return 0; } int copy(char result[MAX_LENGTH], const char *array){ if (strlen(array)+1 > MAX_LENGTH) /* contrôle à faire pour éviter les dépassements d'index pour result */ return -1; /* ça se passe mal */ int i; for (i=0; array[i]!='\0'; i++) result[i] = array[i]; /* copie caractère par caractère dans result */ result[i] = '\0'; /* Ne pas oublier le caractère de fin de chaîne */ return 0; /* ça se passe bien */ }
Code:
#include <stdio.h> #include <string.h> /* pour strlen */ #include <stdlib.h> /* pour malloc et free */ int copy(char **result, const char *array); int main(void){ char *res = NULL; /* déclaration d'un pointeur sur char initialisé à NULL */ const char *test = "Bonjour"; /* déclaration d'un tableau de caractères à copier */ int value; value = copy(&res, test); /* copie de test dans res */ if (value == 0){ puts(res); /* Affichage de res */ free(res); /* Libération de la mémoire */ } return 0; } int copy(char **result, const char *array){ if (array == NULL){ return -1; /* ça se passe mal, rien à copier */ } size_t length = strlen(array); /* taille de la chaîne */ int i; /* Allocation mémoire */ *result = malloc((length * sizeof(char)) + sizeof(char)); /* ne pas oublier de prévoir le caractère supplémentaire '\0' */ if (result == NULL) /* allocation non réussie */ return -1; for (i=0; array[i]!='\0'; i++) (*result)[i] = array[i]; /* copie caractère par caractère dans result */ (*result)[i] = '\0'; /* Ne pas oublier le caractère de fin de chaîne */ return 0; /* ça se passe bien */ }
*result est ce qu'on appelle le déréférencement, spécifique à la récupération de la valeur de l'objet pointé, dans notre cas c'est équivalent à
Code:
char *result = malloc(...);
Alors réfléchissons, le but est de récupérer une chaîne de caractère, copie d'une autre chaîne, on pourrait donc créer une fonction dont la signature serait
Code:
char *copie(const char *array);
Code:
#include <stdio.h> #include <string.h> /* pour strlen */ #include <stdlib.h> /* pour malloc et free */ char *copy(const char *array); int main(void){ char *res = NULL; /* déclaration d'un pointeur sur char initialisé à NULL */ const char *test = "Bonjour"; /* déclaration d'un tableau de caractères à copier */ res = copy(test); /* copie de test, retournée dans la variable res */ if (res != NULL){ puts(res); /* Affichage de res */ free(res); /* Libération de la mémoire */ } return 0; } char *copy(const char *array){ if (array == NULL){ return NULL; /* ça se passe mal, rien à copier, mais on retourne un pointeur, donc NULL*/ } size_t length = strlen(array); /* taille de la chaîne */ int i; /* Allocation mémoire */ char *result = malloc((length * sizeof(char)) + sizeof(char)); /* ne pas oublier de prévoir le caractère supplémentaire '\0' */ if (result == NULL) /* allocation non réussie */ return NULL; /* idem que plus haut pour le cas d'erreur */ for (i=0; array[i]!='\0'; i++) result[i] = array[i]; /* copie caractère par caractère dans result */ result[i] = '\0'; /* Ne pas oublier le caractère de fin de chaîne */ return result; /* retourne le pointeur sur char */ }
Attention, on malloc une fois, on free une fois, si n fois on utilise malloc, alors n fois on utilise free afin de libérer la mémoire allouée, ce qui si on ne fait pas attention peut être une sacré usine à gaz avec des fuites mémoires dans tous les sens. Autant dire que le débogage, risque de provoquer des nuits blanches à certains
AUTRE MANIÈRE D'ALLOUER DE LA MÉMOIRE
D'autres fonctions permettent cela, comme calloc
Le prototype est le suivant :
Code:
void* calloc (size_t num, size_t size);
Code:
char *result = calloc(length+1, sizeof(char)); /* ne pas oublier de prévoir le caractère supplémentaire '\0' */
Et puis il y a des fonctions bien pratique comme strdup, dont le but est justement de copier une chaîne de caractères en allouant de la mémoire
Le prototype est le suivant :
Code:
char *strdup(const char *s);
Le code devient un jeu d'enfant, vous allez apprécier
Code:
#include <stdio.h> #include <string.h> /* pour strlen et strdup */ #include <stdlib.h> /* pour free */ char *copy(const char *array); int main(void){ char *res = NULL; /* déclaration d'un tableau de 256 caractères */ const char *test = "Bonjour"; /* déclaration d'un tableau de caractères à copier */ res = copy(test); /* copie de test, retournée dans la variable res */ if (res != NULL){ puts(res); /* Affichage de res */ free(res); /* Libération de la mémoire */ } return 0; } char *copy(const char *array){ char *result = strdup(array); /* Plus besoin de vérifier si array vaut NULL, cette fonction le fait pour nous */ return result; /* retourne le pointeur sur char */ }
EXERCICE
Un étudiant à envie de calculer sa moyenne de l'année, pour cela, il doit annoncer au programme le nombre de notes (coefficient 1 pour simplifier) à enregistrer.
À partir de cette information, le programme va demander les notes les unes derrière les autres
Code:
Entrer la note n°1: ... Entrer la note n°2: ... Entrer la note n°3: ... ...
Code:
Votre moyenne est: ...
1) Créer une fonction float calculateMean(float *scores, int n) qui calculera la moyenne du tableau de flottants scores à l'aide du nombre de notes n
2) Créer une fonction float *input(int n) qui demandera à l'utilisateur d'enregistrer les notes sous le format spécifié ci-dessus et retournera le tableau de notes enregistré.
Mais je ne vais pas vous laisser comme cela, seul à vous même, pour vous aider, je donne la fonction main qui permettra entre autre de vérifier que vous n'oubliez pas le paramètre d'exécution du programme et le squelette des fonctions à construire...
Code:
#include <stdio.h> #include <stdlib.h> /* pour malloc et free */ float calculateMean(float *scores, int n); float *input(int n); int main(int argc, char *argv[]){ if (argc != 2){ fprintf(stderr, "Mauvaise utilisation du programme !\n"); exit(-1); } int n; int res = sscanf(argv[1], "%u", &n); if (!res || n <= 0){ fprintf(stderr, "On attend un entier supérieur à 0 en paramètre !\n"); exit(-1); } float *tableau = input(n); float moyenne = calculateMean(tableau, n); printf("La moyenne est de: %f\n", moyenne); return 0; } float *input(int n){ float *result = NULL; int i; for (i=0; i<n; i++){ /* Suite du code */ } return result; } float calculateMean(float *scores, int n){ float mean; /* Suite du code */ return mean/n; }