Table des matières

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:

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.

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.

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
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;
}

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.