Annonce

Réduire
Aucune annonce.

Cours langage C n°15 - Allocation dynamique

Réduire
Ceci est une discussion importante.
X
X
 
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • Cours langage C n°15 - Allocation dynamique

    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];
    128 octets sont réservés pour remplir notre tableau de caractères... Oui mais, si j'ai une chaîne de 8 caractères par exemple ? Eh bien tu as 120 octets de ta mémoire utilisée à ne rien faire! La mémoire n'est donc pas gérée de façon optimum !

    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 :
    1. malloc - Alloue de la mémoire
    2. free - Libère la mémoire allouée


    Que dit la documentation ?

    Code:
    void *malloc(size_t size);
    malloc prend un paramètre de type size_t (tiens on sait qu'une fonction comme strlen retourne un type size_t) et retourne une adresse de type void (void *).
    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);
    free prend en paramètre une adresse de type quelconque void * et ne renvoie rien ! Eh oui attention le retour est de type void et non void *

    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 */
    }
    Seulement si je fais un exercice de cryptologie, je ne sais pas si je vais pas éventuellement dépassé les 256 caractères, il serait sympa de rendre cette copie accessible pour des chaînes de plus de 256 caractères. Voilà une solution... avec malloc et free !

    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 */
    }
    Comme vous le voyez c'est assez complexe dans le sens où je demande de modifier dynamiquement le paramètre result, ce qui demande donc de travailler avec l'objet en mémoire (donc une étoile supplémentaire).

    *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(...);
    Bref il vous faudra beaucoup d'entraînement pour arriver à créer ce type de code qui peut même paraître simple, car il faut avoir une maîtrise quasi parfaite sur les pointeurs.

    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);
    Niveau du code, je pense que vous allez apprécier la simplicité

    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 */
    }
    Ah oui, plus de double pointeurs, plus de déréférencement, on a largement simplifié le problème, et en plus on est cohérent par rapport à la demande, car on retourne une adresse qui permettra de lire cette chaîne constituée par la fonction copie.

    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);
    On pourrait avantageusement modifier la ligne 32 de mon précédent code par

    Code:
    char *result = calloc(length+1, sizeof(char)); /* ne pas oublier de prévoir le caractère supplémentaire '\0' */
    Pour le reste du code rien ne change, mais la syntaxe semble moins calculatoire, on demande le nombre de blocs de type char, result sera composé.

    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);
    Fonction prenant une chaîne de caractère est retournant l'adresse résultante de sa copie.

    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 */
    }
    Attention, le piège est d'oublier de libérer la mémoire allouée, en effet indirectement strdup utilise malloc, et on ne le voit pas !

    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: ...
    ...
    et retourner la moyenne de celles-ci

    Code:
    Votre moyenne est: ...
    Pour vous aider on va détailler les différentes démarches

    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;
    }
    Merci de m'avoir lu et à bientôt pour le prochain cours
    Dernière modification par fred, 19 juin 2015, 16h11. Motif: Code squelette modifié !
Chargement...
X