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.
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.
Pour bien comprendre voyons un exemple : supposons que l'on veuille une fonction qui fabrique un tableau 4, 8, 9, 15, 45.
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.
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 ?
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:
tableau,tableauOn dit plutôt libérer une case mémoire plutôt que détruire une case mémoire.
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.
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.
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.
t ligne 14,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.
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.
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.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).malloc(5*sizeof(int)) (ligne 5 ci-dessous)mallocne 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;
}
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.main() se termine, le processus prend fin et toute la mémoire est libérée.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.