====== 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 == 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 == #include #include 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. 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. 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. #include #include 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%%''. int main() { int *t = creer_tableau(); printf("%d\n", t[1]); free(t); return 0; } Nous avons ajouter la ligne 18.