Blog ENI : Toute la veille numérique !
-25€ dès 75€ sur les livres en ligne, vidéos... avec le code FUSEE25. J'en profite !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Gestion de la mémoire

Introduction

Quel meilleur endroit que cette introduction pour fixer les idées sur certaines méthodes de programmation sur les allocations mémoire ? Allouer la mémoire est le sujet traité par la première recette, aussi, nous ne nous étendrons pas sur ce point. Cependant, lorsque vous utilisez malloc(), vous pouvez rencontrer beaucoup de notations, justes, pratiques, fausses ou incorrectes.


#define VARIABLE_NB_ELEMENTS 10 
int *pointeur; 
pointeur = malloc (10 * sizeof (int));                    /* mauvais */ 
pointeur = malloc (VARIABLE_NB_ELEMENTS * sizeof (int));  /* correct */ 
pointeur = malloc (10 * sizeof *pointeur);                /* mauvais */ 
pointeur = malloc (VARIABLE_NB_ELEMENTS * sizeof *pointeur); 
                                                          /* juste   */ 
pointeur = (int *) malloc (10 * sizeof (int));            /* mauvais */ 
pointeur = (int *) malloc (VARIABLE_NB_ELEMENTS * sizeof (int)); 
                                                          /* mauvais */ 
pointeur = (int *) malloc (10);                           /* faux    */ 
 

D’autres déclinaisons de ces expressions existent, mais nous pouvons déjà remarquer trois points. D’abord, le cast, qui est nécessaire en C++ et qui l’était en C par le passé, est inutile et déconseillé aujourd’hui. En effet, il faut inclure le fichier d’en-têtes stdlib.h dans votre code, et quand le préprocesseur n’est pas content, il signifie que vous l’avez oublié.

Ensuite...

Allouer de la mémoire

Problème

Vous souhaitez allouer de la mémoire pour y stocker vos données.

Solution

Utilisez malloc(), calloc(), strdup() ou realloc() en fonction de vos besoins.

La bibliothèque glib propose quelques fonctions équivalentes à celles-ci : g_malloc(), g_malloc0(), g_realloc(), g_try_malloc() et g_try_realloc().

Il existe également deux fonctions pour allouer de la mémoire dite alignée, c’est-à-dire à une adresse multiple d’un nombre fourni en argument : posix_memalign() et aligned_alloc().

Discussion

Les fonctions malloc() et calloc() ont un rôle proche : elles allouent la mémoire demandée par l’utilisateur. Mais calloc() remet à zéro l’espace mémoire alloué alors que malloc() ne fait qu’allouer la mémoire. La fonction strdup() est plus connue pour être la fonction qui affecte une chaîne de caractères à un pointeur tout en s’occupant de l’allocation mémoire. Quant à la fonction realloc(), elle permet surtout d’agrandir un espace mémoire déjà alloué ou de le réduire.

Alors que ces fonctions renvoient toutes NULL en cas d’échec d’allocation mémoire, les fonctions g_malloc(), g_malloc0() et g_realloc() arrêtent le programme, ce qui n’oblige pas à tester la valeur renvoyée. Pour ne pas quitter le programme en cas d’erreur d’allocation mémoire, utilisez g_try_malloc()...

Créer son gestionnaire de mémoire

Problème

Vous voulez optimiser la gestion de la mémoire car malloc() et realloc() prennent trop de temps à votre goût.

Solution

Créez vos propres fonctions d’allocation mémoire et de libération de la mémoire, ou utilisez le gestionnaire de mémoire de glib. Pensez à utiliser les GStrings de glib pour les chaînes de caractères.

Discussion

Les gestionnaires de mémoire apparaissent en général lorsqu’il est nécessaire d’allouer une certaine quantité de mémoire, mais que vous ne savez pas combien de mémoire allouer avant d’avoir toutes les données à stocker. Le principe consiste alors à allouer plus de mémoire qu’il n’en faut, et à ne l’étendre que si nécessaire. Différents types de gestionnaires de mémoire existent. Le plus simple consiste à créer une fonction qui teste s’il faut allouer de la mémoire et ne le fait que si c’est nécessaire :


#define BLOCK_SIZE 1024 
 
char * 
mem_realloc_if_necessary (char *p, int len) 
{ 
  char *real_p = NULL; /* Ce pointeur correspond à p - sizeof(int). 
                        * La longueur de la chaîne se trouve 
                        * dans la suite entre real_p et p. 
                        */ 
  int real_len; 
  if (p != NULL) 
    { 
      /* Si p est non nul, nous retrouvons la longueur de la zone ...

Redéfinir les fonctions d’allocation de mémoire

Problème

Vous avez besoin d’utiliser vos propres fonctions d’allocation de mémoire, par exemple pour y ajouter un outil de traçage des allocations/libérations de mémoire. 

Solution

Créez votre propre fonction d’allocation mémoire, puis utilisez #define pour affecter au nom de la fonction standard votre nouvelle fonction.

Discussion

Voici un exemple de redéfinition de malloc() qui arrête le programme si l’allocation a échoué :


/* fichier.c */ 
#include <stdlib.h> 
 
void * 
my_malloc (size_t size) 
{ 
  void *p; 
  if (NULL == (p = malloc (size))) 
    exit (EXIT_FAILURE); 
  return (p); 
} 
 
/* fichier.h */ 
#include <stdlib.h> 
void *my_malloc (size_t size); 
#define malloc(x) my_malloc(x)
 

Attention ! L’affectation de votre nouvelle fonction au nom de la fonction standard avec #define ne doit surtout pas être effectuée avant la définition de votre fonction car celle-ci utilise la fonction malloc() standard. Sinon, cela reviendrait à une définition récursive sans condition d’arrêt et le préprocesseur vous le ferait remarquer. Dans l’exemple ci-dessus, vous ne devez donc pas inclure le fichier d’en-têtes fichier.h dans le fichier...

Tracer les allocations de mémoire

Problème

Vous voulez savoir quand de la mémoire a été allouée et quand elle a été libérée pour détecter des fuites de mémoire.

Solution

Utilisez un outil de profilage de mémoire comme celui fourni par glib, dmalloc ou autres ; ou programmez simplement le vôtre en l’adaptant à vos besoins particuliers puis en redéfinissant les fonctions d’allocation.

Si vous disposez de l’outil Valgrind, utilisez-le en priorité surtout pour rechercher une fuite mémoire ou, au contraire, pour vous assurer de l’abscence de fuite.

Discussion

Utilisation des outils inclus dans glib

L’outil de profilage de glib s’utilise très simplement et est disponible sur tous les systèmes d’exploitation où glib est portée. Son utilisation se limite à un appel à g_mem_set_vtable(glib_mem_profiler_table) en tout début de programme avant tout autre appel à une fonction de glib. Mais elle oblige à utiliser les fonctions d’allocation de glib comme g_malloc() et d’autres vues dans la recette "Allouer de la mémoire". Voyez l’exemple dans cette recette.

Utilisation de la bibliothèque dmalloc

La bibliothèque dmalloc est assez répandue et mérite que nous nous y attardions. Pour l’utiliser, commencez par placer #include <dmalloc.h> après toutes les autres lignes contenant #include. Puis compilez le programme avec les options supplémentaires suivantes : -DDMALLOC -ldmalloc. Lancez le programme comme si de rien n’était, et il génère un fichier de journalisation en fonction des paramètres spécifiés dans la variable d’environnement DMALLOC_OPTIONS. Cette variable d’environnement peut être définie à l’aide de l’outil en ligne de commande dmalloc avec les options adéquates, dont la liste s’obtient avec dmalloc --usage-long.

En lançant dmalloc -b high -i 100 -l dmallog.log, les lignes à exécuter pour un shell Bourne (comme sh ou bash) sont affichées et elles précisent les options suivantes : débogage fin ; vérification de la pile toutes les 100 opérations ; écriture des résultats dans...

Créer de la mémoire partagée entre processus

Problème

Vous souhaitez allouer de la mémoire qui soit accessible depuis d’autres processus. 

Solution

Utilisez les fonctions de communication interprocessus (IPC) shmget(), shmat(), shmdt() et éventuellement shmop().

Discussion

La mémoire partagée s’obtient en deux étapes. L’une consiste à la réserver si elle ne l’est pas déjà, et à obtenir son identifiant. Utilisez shmget() pour cela. L’autre attache le segment de mémoire partagée au segment de mémoire du processus avec shmat(). Voici un exemple qui utilise ftok() afin d’obtenir ou allouer de la mémoire partagée correspondant à un nom de fichier et un nom de projet :


int shmid = -1; 
 
int 
shm_init (char *filename, char project_id, int size) 
{ 
/* Crée un jeu de 1 segment de mémoire partagée */ 
  if (-1 == shmid) 
    { 
      key_t key; 
      shmid = shmget (key = ftok (filename, project_id), size, 0600); 
      if (-1 == shmid) 
        {                     /* Le segment de mémoire partagée 
                               * n'existe pas ou est inutilisable. */ 
          shmid = shmget (k, size, IPC_CREAT | 0600); 
          if (-1 == shmid) 
            {                 /* Le segment de mémoire partagée  
                               * n'a pas pu être créé. */ 
              fprintf (stderr, "Impossible de créer le segment de " 
                        "mémoire partagée (errno=%d)", errno); 
              exit (EXIT_FAILURE); 
            } ...