jeudi 18 juin 2015

Cours langage C n°15 - Allocation dynamique

Allocation dynamique

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 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 */
        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 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){

    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 :)

Ce cours n'est pas terminé, il y a tellement à dire, que j'en oublierais encore, même en essayant de rien oublier !


from Hackademics : Forum de hacking – hackers white hat – cours de securite informatique, apprendre langage python, tutoriels de reverse engineering http://ift.tt/1G7kh9u
via IFTTT

Aucun commentaire:

Enregistrer un commentaire