587 votes

Que se passe-t-il VRAIMENT quand on ne libère pas après malloc ?

C'est quelque chose qui me dérange depuis longtemps.

On nous a tous appris à l'école (du moins, moi) que vous DEVEZ libérer chaque pointeur alloué. Je suis un peu curieux, cependant, de connaître le coût réel de la non-libération de la mémoire. Dans certains cas évidents, comme lorsque malloc est appelé à l'intérieur d'une boucle ou d'une partie de l'exécution d'un thread, il est très important de le libérer pour éviter les fuites de mémoire. Mais considérez les deux exemples suivants :

D'abord, si j'ai un code qui est quelque chose comme ça :

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

Quel est le véritable résultat ici ? Je pense que le processus meurt et que l'espace du tas a disparu de toute façon, donc il n'y a pas de mal à manquer l'appel à free (cependant, je reconnais l'importance de l'avoir quand même pour la fermeture, la maintenabilité et les bonnes pratiques). Ai-je raison de penser ainsi ?

Deuxièmement, disons que j'ai un programme qui agit un peu comme un shell. Les utilisateurs peuvent déclarer des variables comme aaa = 123 et ceux-ci sont stockés dans une structure de données dynamique pour une utilisation ultérieure. Il semble évident que vous utiliserez une solution qui fera appel à une fonction *alloc (hashmap, liste liée, quelque chose comme ça). Pour ce type de programme, cela n'a pas de sens de ne jamais libérer après l'appel de la fonction malloc parce que ces variables doivent être présentes à tout moment pendant l'exécution du programme et qu'il n'y a pas de bon moyen (à ma connaissance) d'implémenter cela avec de l'espace alloué statiquement. Est-ce que c'est une mauvaise conception d'avoir un tas de mémoire qui est allouée mais seulement libérée dans le cadre de la fin du processus ? Si oui, quelle est l'alternative ?

8 votes

@NTDLS La magie du système d'évaluation fonctionne pour une fois : 6 ans plus tard, la réponse "plus méritante" a effectivement atteint le sommet.

22 votes

Les personnes ci-dessous ne cessent de dire qu'un bon système d'exploitation moderne fait le nettoyage, mais que se passe-t-il si le code s'exécute en mode noyau (par exemple, pour des raisons de performances) ? Les programmes en mode noyau (dans Linux par exemple) sont-ils sandboxés ? Si ce n'est pas le cas, je crois qu'il faudrait alors tout libérer manuellement, je suppose, même avant toute terminaison anormale comme avec abort().

6 votes

@Dr.PersonPersonII Oui, le code s'exécutant en mode noyau doit généralement tout libérer manuellement.

409voto

Paul Tomblin Points 83687

Presque tous les systèmes d'exploitation modernes récupèrent tout l'espace mémoire alloué après la sortie d'un programme. La seule exception à laquelle je peux penser pourrait être quelque chose comme Palm OS où le stockage statique du programme et la mémoire d'exécution sont à peu près la même chose, donc ne pas libérer pourrait faire que le programme prenne plus de stockage. (Je ne fais que spéculer ici).

Donc, en général, il n'y a pas de mal à cela, sauf le coût d'exécution d'avoir plus de stockage que nécessaire. Dans l'exemple que vous donnez, vous voulez certainement conserver la mémoire d'une variable qui pourrait être utilisée jusqu'à ce qu'elle soit effacée.

Cependant, il est de bon ton de libérer la mémoire dès que vous n'en avez plus besoin, et de libérer tout ce qui reste en mémoire à la sortie du programme. Il s'agit plutôt d'un exercice consistant à savoir quelle mémoire vous utilisez et à vous demander si vous en avez encore besoin. Si vous ne gardez pas de trace, vous risquez d'avoir des fuites de mémoire.

D'un autre côté, la recommandation similaire de fermer vos fichiers à la sortie a un résultat beaucoup plus concret - si vous ne le faites pas, les données que vous avez écrites dans ces fichiers peuvent ne pas être vidées, ou s'il s'agit d'un fichier temporaire, elles peuvent ne pas être supprimées lorsque vous avez terminé. De même, les transactions des handles de base de données doivent être validées puis fermées lorsque vous en avez terminé avec eux. De même, si vous utilisez un langage orienté objet comme le C++ ou l'Objective C, ne pas libérer un objet lorsque vous avez fini de l'utiliser signifie que le destructeur ne sera jamais appelé et que toutes les ressources dont la classe est responsable ne seront pas nettoyées.

18 votes

Il serait probablement bon aussi de mentionner que tout le monde n'utilise pas un système d'exploitation moderne, si quelqu'un prend votre programme (et qu'il fonctionne toujours sur un OS qui ne récupère pas la mémoire) l'exécute alors GG.

87 votes

Je considère vraiment que cette réponse est fausse. On devrait toujours désallouer les ressources après les avoir utilisées, qu'il s'agisse de handles de fichiers, de mémoire ou de mutexs. En ayant cette habitude, on ne fera pas ce genre d'erreur en construisant des serveurs. Certains serveurs sont censés fonctionner 24 heures sur 24, 7 jours sur 7. Dans ce cas, toute fuite de quelque nature que ce soit signifie que votre serveur finira par manquer de cette ressource et se bloquera ou se plantera d'une manière ou d'une autre. Un programme utilitaire court, ou une fuite n'est pas si grave. Un serveur, une fuite, c'est la mort. Faites-vous une faveur. Nettoyez après vous. C'est une bonne habitude.

139 votes

Quelle partie de "Cependant, il est considéré comme un bon style de libérer la mémoire dès que vous n'en avez plus besoin, et de libérer tout ce que vous avez encore autour à la sortie du programme." considérez-vous comme erroné, alors ?

119voto

compie Points 3773

Oui, vous avez raison, votre exemple ne fait pas de mal (du moins pas sur la plupart des systèmes d'exploitation modernes). Toute la mémoire allouée par votre processus sera récupérée par le système d'exploitation une fois que le processus aura quitté.

Source : Mythes sur l'allocation et le GC (Alerte PostScript !)

Mythe de l'allocation 4 : Les programmes qui ne collectent pas de carburants doivent toujours désallouer toute la mémoire qu'ils allouent.

La vérité : Omis omises dans le code fréquemment exécuté fréquemment exécuté provoquent des fuites croissantes. Elles sont rarement acceptables, mais les programmes qui conservent la plupart de la mémoire allouée jusqu'à jusqu'à la sortie du programme sont souvent plus performants sans aucune désallocation intermédiaire. Malloc est beaucoup plus facile à implémenter si s'il n'y a pas de free.

Dans la plupart des cas, désallouer la mémoire juste avant la sortie du programme est inutile. Le système d'exploitation le récupérera de toute façon. Free touchera et consultera les objets morts objets morts ; l'OS ne le fera pas.

Conséquence : Soyez prudent avec les "détecteurs de détecteurs de fuites" qui comptent les allocations. Certaines "fuites" sont bonnes !

Cela dit, vous devriez vraiment essayer d'éviter toutes les fuites de mémoire !

Deuxième question : votre conception est correcte. Si vous avez besoin de stocker quelque chose jusqu'à la fin de votre application, vous pouvez le faire avec l'allocation dynamique de mémoire. Si vous ne connaissez pas la taille requise à l'avance, vous ne pouvez pas utiliser la mémoire allouée statiquement.

3 votes

Peut-être parce que la question, telle que je l'ai lue, est de savoir ce qui se passe réellement avec la mémoire qui fuit, et non de savoir si cet exemple spécifique est correct. Je ne la rejetterais pas pour autant, car c'est toujours une bonne réponse.

3 votes

Probablement qu'il y avait (au début de Windows, au début de Mac OS), et qu'il y a peut-être encore, des systèmes d'exploitation qui exigent que les processus libèrent la mémoire avant de quitter, sinon l'espace n'est pas récupéré.

0 votes

C'est tout à fait normal, sauf si vous vous souciez de la fragmentation de la mémoire ou de l'épuisement de la mémoire - si vous le faites trop souvent, les performances de vos applications disparaîtront. Outre les faits concrets, il faut toujours suivre les meilleures pratiques et prendre de bonnes habitudes.

64voto

Trevor Boyd Smith Points 3513

\=== Qu'en est-il la garantie de l'avenir et réutilisation du code ? ===

Si vous Ne le fais pas. si vous écrivez le code pour libérer les objets, vous limitez le code à une utilisation sûre uniquement lorsque vous pouvez compter sur la libération de la mémoire par le processus en cours de fermeture... c'est-à-dire pour les petits projets à usage unique ou les projets "à jeter". [1] projets)... où vous savez quand le processus se terminera.

Si vous faire Si vous écrivez le code qui libère la mémoire allouée dynamiquement, vous protégez le code pour l'avenir et vous permettez à d'autres de l'utiliser dans un projet plus vaste.


[1] concernant les projets "à jeter". Le code utilisé dans les projets "jetables" a une façon de ne pas être jeté. En effet, dix ans plus tard, votre code "jetable" est toujours utilisé).

J'ai entendu l'histoire d'un type qui a écrit du code juste pour le plaisir afin de faire fonctionner son matériel mieux. Il a dit " juste un hobby, ne sera pas grand et professionnel ". Des années plus tard, de nombreuses personnes utilisent son code "hobby".

16 votes

Descendu pour "petits projets". Il y a beaucoup de grands projets qui très intentionnellement ne pas libérer la mémoire à la sortie car c'est une perte de temps si vous connaissez vos plates-formes cibles. IMO, un exemple plus précis aurait été "projets isolés". Par exemple, si vous créez une bibliothèque réutilisable qui sera incluse dans d'autres applications, il n'y a pas de point de sortie bien défini et vous ne devriez pas perdre de mémoire. Dans le cas d'une application autonome, vous saurez toujours exactement quand le processus se termine et vous pourrez décider consciemment de confier le nettoyage au système d'exploitation (qui doit effectuer les vérifications dans tous les cas).

3 votes

L'application d'hier est la fonction de bibliothèque d'aujourd'hui, et demain elle sera liée à un serveur à longue durée de vie qui l'appellera des milliers de fois.

1 votes

@AdrianMcCarthy : Si une fonction vérifie si un pointeur statique est nul, l'initialise avec malloc() s'il l'est, et se termine si le pointeur est toujours nul, une telle fonction peut être utilisée en toute sécurité un nombre arbitraire de fois même si free n'est jamais appelé. Je pense qu'il est probablement utile de distinguer les fuites de mémoire qui peuvent utiliser une quantité illimitée de stockage, des situations qui ne peuvent gaspiller qu'une quantité finie et prévisible de stockage.

58voto

DigitalRoss Points 80400

Vous avez raison, il n'y a pas de mal et c'est plus rapide de sortir.

Il y a plusieurs raisons à cela :

  • Tous les environnements de bureau et de serveur libèrent simplement l'intégralité de l'espace mémoire lors de la fonction exit(). Ils ne sont pas conscients des structures de données internes au programme telles que les tas.

  • Presque tous free() mises en œuvre ne jamais rendre la mémoire au système d'exploitation de toute façon.

  • Plus important encore, c'est une perte de temps lorsqu'elle est effectuée juste avant exit(). À la sortie, les pages de mémoire et l'espace swap sont simplement libérés. En revanche, une série d'appels à free() brûle du temps CPU et peut entraîner des opérations de pagination du disque, des manques de cache et des évictions de cache.

En ce qui concerne le possibilité de la réutilisation future du code justifiant la certitude d'opérations inutiles : c'est un élément à prendre en compte, mais ce n'est sans doute pas le plus important. Agile manière. YAGNI !

2 votes

J'ai travaillé une fois sur un projet où nous avons passé un court laps de temps à essayer de comprendre l'utilisation de la mémoire d'un programme (on nous a demandé de le prendre en charge, nous ne l'avons pas écrit). Sur la base de cette expérience, je suis d'accord avec votre deuxième point. Cependant, j'aimerais vous entendre (ou quelqu'un d'autre) fournir plus de preuves que cela est vrai.

3 votes

Pas grave, j'ai trouvé la réponse : stackoverflow.com/questions/1421491/ . Merci beaucoup !

0 votes

@aviggiano ça s'appelle YAGNI.

15voto

Antti Huima Points 15465

Il n'y a aucun problème à laisser de la mémoire non libérée lorsque vous quittez le processus ; malloc() alloue la mémoire à partir de la zone de mémoire appelée "le tas", et le tas complet d'un processus est libéré lorsque le processus quitte.

Ceci étant dit, l'une des raisons pour lesquelles les gens insistent encore sur le fait qu'il est bon de tout libérer avant de sortir est que les débogueurs de mémoire (par exemple valgrind sous Linux) détectent les blocs non libérés comme des fuites de mémoire, et si vous avez également de "vraies" fuites de mémoire, il devient plus difficile de les repérer si vous obtenez également de "faux" résultats à la fin.

1 votes

Valgrind ne fait-il pas très bien la distinction entre "fuite" et "encore accessible" ?

13 votes

-C'est une mauvaise pratique de codage que de laisser de la mémoire allouée sans la libérer. Si ce code était extrait dans une bibliothèque, il provoquerait des fuites de mémoire un peu partout.

6 votes

+1 pour compenser. Voir la réponse de compie. free à l'adresse exit temps considéré comme nuisible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X