Outils pour utilisateurs

Outils du site


nsi:langages:c:malloc

Mémoire et malloc

C, on a eu l'occasion de le dire, est proche de la machine et permet un contrôle fin de l'utilisation des capacités de la machine. Cela passe par une suppression de certains automatismes qui facilitent l'utilisation d'autres langages.

processus

Petit rappel terminologique : le programme est le fichier texte dans lequel on écrit en langage C ou autre. Quand le programme est exécuté, on parle de processus. Puisque nous allons parler de l'utilisation mémoire lors de l'exécution du programme, il faudra parler de processus.

Exemple

Pour bien comprendre voyons un exemple : supposons que l'on veuille une fonction qui fabrique un tableau 4, 8, 9, 15, 45.

Python

exemple en python

def creer_tableau():
    tableau = [4, 8, 9, 15, 45]
    return tableau

t = creer_tableau()
print(t[1])

Ce code fonctionne sans difficulté et on n'imagine pas qu'il puisse créer un problème car on a l'habitude de certains automatismes. Le C met à jour ces automatismes et va nous montrer que ce n'est pas si simple.

C

exemple c

#include <stdio.h>
#include <stdlib.h>

int* creer_tableau(){
  int tableau[5] = {4, 8, 9, 15, 45};
  return tableau;
}

int main()
{
   int *t = creer_tableau();
   printf("%d\n", t[1]);
   return 0;
}

La compilation ne passe pas. Pourquoi ?

Variable locale

La variable tableau créée dans creer_tableau est locale, c'est à dire qu'elle ne devrait vivre que le temps de l'exécution de la fonction creer_tableau. Sitôt la fonction terminée, l'espace mémoire réservé pour le contenu du tableau doit être libéré.

Je rappelle que la variable tableau contient l'adresse mémoire de la première case du tableau. C'est un pointeur (d'où la déclaration int * ligne 4).

Alors:

  • La fonction réserve des cases mémoires commençant à l'adresse tableau,
  • La fonction renvoie l'adresse tableau
  • En même temps qu'elle renvoie l'adresse, la fonction détruit le contenu en mémoire, si bien que l'adresse ne pointe plus sur rien !
On dit plutôt libérer une case mémoire plutôt que détruire une case mémoire.
Ça marche en Python

Python utilise des mécanismes automatiques qui dans ce genre de cas va empêcher la libération des cases mémoires réservées dans la fonction quand on en a encore besoin après la fin de la fonction.

Cela signifie que Python décide automatiquement quelles mémoires il doit conserver et quelles mémoires il doit libérer. Cela fonctionne très bien en général mais n'est pas forcément optimum.

Le heap et le stack

heap = tas, stack = pile

Décrivons ici comment son gérées ces mémoires.

  • Le système d'exploitation alloue au processus un espace mémoire. Le processus ne peut pas accéder n'importe comment à la mémoire physique de l'ordinateur sinon rien n'empêcherait un processus malicieux d'aller perturber les zones mémoires des autres processus.
  • Le processus gère donc sa zone mémoire, allouée par l'OS. Il va l'organiser en deux parties, la pile et le tas.
  • La pile correspond aux zones mémoires qui correspondent aux fonctions. Quand une fonction débute son exécution, ses variables locales sont placées dans la pile. Et lorsque la fonction se termine, ses variables locales sont dépilées (donc libérées)
  • Le tas est une zone qui sera accessible différemment et dans laquelle on pourra créer des mémoires sans qu'elles soit dépilées.

Mais attention ! Quand on crée une mémoire dans le tas, cette mémoire ne se libérera pas toute seule.

Toute la mémoire est néanmoins libérée à la fin du processus !

Cela veut dire que si on crée beaucoup de variable dont la mémoire est dans le tas, on risque de faire gonfler la consommation mémoire du processus. Il faut donc veiller à libérer les mémoires ainsi créées.

Réserver la mémoire où on l'utilise

Une première solution à ce problème est réserver l'espace nécessaire à l'endroit où on en aura besoin.

Si j'ai besoin du tableau dans main, je réserve la mémoire dans le main, pas dans la fonction creer_tableau.

  • On réserve donc le tableau t ligne 14,
  • Ligne 15, on transmet t à la fonction creer_tableau pour qu'elle écrive le contenu du tableau.

exemple c

int main()
{
   int t[5];
   creer_tableau(t);
   printf("%d\n", t[1]);
   return 0;
}

Dans ce cas il faut adapter la fonction creer_tableau qui n'a plus à renvoyer un tableau, puisque le tableau lui est fourni, la fonction n'a qu'à le remplir.

exemple c

void creer_tableau(int *tableau){
  tableau[0] = 4;
  tableau[1] = 8;
  tableau[2] = 9;
  tableau[3] = 15;
  tableau[4] = 45;
}

L'avantage de cette méthode est que l'on n'a pas à se soucier du temps de vie de l'espace mémoire réservé. C libérera la variable t suivant un mécanisme normal.

Le malloc

Toutes les déclarations vues jusqu'à présent, comme int i = 12; créait les variables dans la pile. Quand on veut créer une variable dans le tas, il faut utiliser malloc.

Comprendre memory allocation. On qualifie cette allocation de dynamique.

malloc renvoie un pointeur et il faut préciser la taille nécessaire.

Cas du tableau
  • nous voulions créer tableau = {4, 8, 9, 15, 45}, c'est donc un tableau de 5 entiers. On a donc besoin d'une zone mémoire dont la taille est de 5 int.
  • Mais quelle est la taille en mémoire d'un int ? On sait qu'un int, en C, occupe 2 octets. Mais le mieux est d'utiliser sizeof(int) qui répond directement à la question. Notre tableau occupe donc 5*sizeof(int).
  • L'allocation dynamique (dans le tas) se fera donc par malloc(5*sizeof(int)) (ligne 5 ci-dessous)
malloc ne sera pas compris par un compilateur C++. Si vous utilisez C playground, pensez à configurer la compilation en C.

allouer avec malloc

#include <stdio.h>
#include <stdlib.h>

int* creer_tableau(){
  int *tableau = malloc(5*sizeof(int));
  if (tableau == NUll){
      exit(1); // interrompt l'exécution avec erreur
  }
  tableau[0] = 4;
  tableau[1] = 8;
  tableau[2] = 9;
  tableau[3] = 15;
  tableau[4] = 45;
  return tableau;
}

int main()
{
   int *t = creer_tableau();
   printf("%d\n", t[1]);
   return 0;
}
  • À présent, tableau n'est plus dans la pile, l'espace mémoire n'est plus libéré avec la fin de creer_tableau. t (ligne 19) pointe donc sur une zone mémoire valide.
  • Même si le processus dure encore longtemps,et même si on ne se sert plus du tableau, la zone mémoire reste réservée.
  • Quand la fonction main() se termine, le processus prend fin et toute la mémoire est libérée.
  • Remarquez l'ajout d'un test, lignes 6 à 8, qui interrompt le processus s'il n'a pas été possible de réserver l'espace mémoire.

Libérer une mémoire du tas

Puisque la libération de la mémoire ne se fait plus automatiquement, il peut devenir utile de demander explicitement la libération, pour limiter la consommation mémoire du processus.

Il faut utiliser la fonction free.

Libérer avec free

int main()
{
   int *t = creer_tableau();
   printf("%d\n", t[1]);
   free(t);
   return 0;
}

Nous avons ajouter la ligne 18.

nsi/langages/c/malloc.txt · Dernière modification : de goupillwiki