Est-il possible de ne pas utiliser free () sur la mémoire allouée?

J’étudie l’ingénierie informatique et j’ai des cours d’électronique. J’ai entendu dire par deux de mes professeurs (de ces cours) qu’il est possible d’éviter d’utiliser la fonction free() après malloc() , calloc() , etc.) car les espaces mémoire alloués ne seront probablement plus utilisés pour allouer d’autres mémoires. C’est-à-dire, par exemple, si vous allouez 4 octets et que vous les relâchez, vous disposerez de 4 octets d’espace qui ne seront probablement plus atsortingbués: vous aurez un trou .

Je pense que c’est fou: vous ne pouvez pas avoir un programme non-jouet où vous allouez de la mémoire sur le tas sans le libérer. Mais je n’ai pas les connaissances nécessaires pour expliquer exactement pourquoi il est si important que pour chaque malloc() il doit y avoir un free() .

Donc, y a-t-il des circonstances dans lesquelles il pourrait être approprié d’utiliser un malloc() sans utiliser free() ? Et si non, comment puis-je expliquer cela à mes professeurs?

Facile: il suffit de lire le code source de toute implémentation semi-sérieuse de malloc()/free() . J’entends par là le gestionnaire de mémoire réel qui gère le travail des appels. Cela peut être dans la bibliothèque d’exécution, la machine virtuelle ou le système d’exploitation. Bien entendu, le code n’est pas également accessible dans tous les cas.

Faire en sorte que la mémoire ne soit pas fragmentée en joignant des trous adjacents dans des trous plus grands est très courant. Des responsables plus sérieux utilisent des techniques plus sérieuses pour y parvenir.

Donc, supposons que vous faites trois allocations et désallocations et que vous obtenez des blocs mis en mémoire dans cet ordre:

 +-+-+-+ |A|B|C| +-+-+-+ 

La taille des allocations individuelles importe peu. alors vous libérez le premier et le dernier, A et C:

 +-+-+-+ | |B| | +-+-+-+ 

quand vous libérez enfin B, vous (initialement, au moins en théorie) vous retrouvez avec:

 +-+-+-+ | | | | +-+-+-+ 

qui peut être fragmenté en juste

 +-+-+-+ | | +-+-+-+ 

c’est-à-dire un seul bloc libre plus grand, aucun fragment restant.

Références, comme demandé:

  • Essayez de lire le code pour dlmalloc . Je suis beaucoup plus avancé, étant une implémentation complète de la qualité de la production.
  • Même dans les applications intégrées, les implémentations de désagrégation sont disponibles. Voir par exemple ces notes sur le code heap4.c dans FreeRTOS .

Les autres réponses expliquent déjà parfaitement que les implémentations réelles de malloc() et free() coalisent effectivement (défragmenter) les trous en gros morceaux libres. Mais même si ce n’était pas le cas, ce serait toujours une mauvaise idée de renoncer à la free() .

La chose est, votre programme vient d’allouer (et veut libérer) ces 4 octets de mémoire. S’il doit fonctionner pendant une période prolongée, il est probable qu’il ne lui faudra plus que 4 octets de mémoire. Ainsi, même si ces 4 octets ne fusionnent jamais dans un espace contigu plus grand, ils peuvent toujours être réutilisés par le programme lui-même.

C’est un non-sens total, par exemple il y a beaucoup d’implémentations différentes de malloc , certaines essayent de rendre le tas plus efficace comme celui de Doug Lea ou celui- ci.

Vos professeurs travaillent-ils avec POSIX, par hasard? Si elles ont l’habitude d’écrire beaucoup de petites applications shell minimalistes, je peux imaginer que cette approche ne serait pas si mauvaise: libérer tout le tas en même temps à loisir est plus rapide que de libérer mille variables. Si vous prévoyez que votre application fonctionnera pendant une seconde ou deux, vous pourrez facilement vous en sortir sans aucune désaffectation.

C’est toujours une mauvaise pratique bien sûr (l’amélioration des performances devrait toujours être basée sur le profilage, pas sur des sentiments vagues), et ce n’est pas quelque chose que vous devriez dire aux élèves sans expliquer les autres contraintes. -applications à écrire de cette façon (si vous n’utilisez pas une allocation statique pure et simple). Si vous travaillez sur quelque chose qui ne nécessite pas de libérer vos variables, vous travaillez soit dans des conditions de très faible latence (dans ce cas, comment pouvez-vous vous permettre une allocation dynamic et C ++?: D), ou faire quelque chose de très, très mal (comme allouer un tableau entier en allouant mille entiers les uns après les autres plutôt qu’un seul bloc de mémoire).

Vous avez mentionné qu’ils étaient professeurs d’électronique. Ils peuvent être utilisés pour écrire des logiciels en temps réel ou des micrologiciels, être en mesure de chronométrer avec précision le temps d’exécution requirejs. Dans les cas où vous savez que vous disposez de suffisamment de mémoire pour toutes les allocations et que vous ne libérez pas et ne réallouez pas la mémoire, vous pouvez calculer la limite de temps d’exécution plus facilement.

Dans certains schémas, la protection de la mémoire matérielle peut également être utilisée pour s’assurer que la routine se termine dans sa mémoire allouée ou génère un piège dans ce qui devrait être des cas très exceptionnels.

Sous un angle différent de celui des commentaires et réponses précédents, il est possible que vos professeurs aient déjà expérimenté des systèmes où la mémoire était allouée de manière statique (à savoir lorsque le programme était compilé).

L’allocation statique vient quand vous faites des choses comme:

 define MAX_SIZE 32 int array[MAX_SIZE]; 

Dans de nombreux systèmes temps réel et embarqués (ceux qui sont les plus susceptibles d’être rencontrés par les EE ou les EC), il est généralement préférable d’éviter l’allocation dynamic de mémoire. Ainsi, les utilisations de malloc , new et de leurs homologues de suppression sont rares. De plus, la mémoire des ordinateurs a explosé ces dernières années.

Si vous avez 512 Mo à votre disposition, et que vous allouez statiquement 1 Mo, vous avez environ 511 Mo à parcourir avant que votre logiciel explose (enfin, pas exactement … mais allez-y avec moi). En supposant que vous ayez 511 Mo à abuser, si vous faites 4 octets par seconde sans les libérer, vous pourrez courir pendant près de 73 heures avant de manquer de mémoire. Étant donné que de nombreuses machines sont fermées une fois par jour, cela signifie que votre programme ne sera jamais à court de mémoire!

Dans l’exemple ci-dessus, la fuite est de 4 octets par seconde, soit 240 octets / min. Imaginez maintenant que vous réduisez ce ratio octet / min. Plus ce ratio est bas, plus votre programme peut fonctionner sans problèmes. Si vos malloc sont rares, c’est une possibilité réelle.

Heck, si vous savez que vous allez seulement à malloc quelque chose une fois, et que malloc ne sera plus jamais frappé, alors c’est comme l’allocation statique, bien que vous n’ayez pas besoin de connaître la taille de ce que vous allouez à l’avant. Par exemple: Disons que nous avons encore 512 Mo. Nous devons malloc 32 tableaux d’entiers. Ce sont des entiers typiques – 4 octets chacun. Nous soaps que les tailles de ces tableaux ne dépasseront jamais 1024 entiers. Aucune autre allocation de mémoire ne se produit dans notre programme. Avons-nous assez de mémoire? 32 * 1024 * 4 = 131,072. 128 Ko – alors oui. Nous avons beaucoup d’espace. Si nous soaps que nous n’allouerons jamais plus de mémoire, nous pouvons sans risque les canaliser sans les libérer. Cependant, cela peut également signifier que vous devez redémarrer la machine / le périphérique si votre programme plante. Si vous démarrez / arrêtez votre programme 4 096 fois, vous allouerez tous les 512 Mo. Si vous avez des processus zombie, il est possible que la mémoire ne soit jamais libérée, même après un crash.

Epargnez-vous la douleur et la misère, et consumz ce mantra comme une vérité unique: malloc devrait toujours être associé à une free . new devrait toujours avoir une delete .

Je pense que l’affirmation énoncée dans la question est un non-sens si elle est prise littéralement du sharepoint vue du programmeur, mais elle a la vérité (du moins certaines) du sharepoint vue du système d’exploitation.

malloc () finira par appeler soit mmap () soit sbrk () pour récupérer une page du système d’exploitation.

Dans tout programme non sortingvial, les chances que cette page soit restituée au système d’exploitation pendant la durée de vie d’un processus sont très faibles, même si vous libérez la majeure partie de la mémoire allouée. Donc, la mémoire libre () ne sera disponible pour le même processus que la plupart du temps, mais pas pour les autres.

Vos professeurs ne sont pas faux, mais ils le sont aussi (ils sont au moins trompeurs ou trop simplistes). La fragmentation de la mémoire pose des problèmes de performances et d’utilisation efficace de la mémoire. Vous devez donc parfois l’examiner et prendre des mesures pour l’éviter. Un truc classique est, si vous allouez beaucoup de choses de la même taille, de saisir un pool de mémoire au démarrage qui est un peu de cette taille et de gérer son utilisation entièrement en interne, garantissant ainsi que la fragmentation ne se produit pas. Niveau OS (et les trous dans votre mappeur de mémoire interne auront exactement la bonne taille pour le prochain object de ce type qui arrive).

Il y a des bibliothèques tierces entières qui ne font que gérer ce genre de choses pour vous, et parfois c’est la différence entre une performance acceptable et quelque chose qui fonctionne trop lentement. malloc() et free() prennent un temps considérable à exécuter, ce que vous remarquerez si vous les appelez beaucoup.

Donc, en évitant simplement d’utiliser malloc() et free() vous éviterez les problèmes de fragmentation et de performance, mais vous devez toujours vous assurer de free() tout ce que vous faites sous malloc() moins bonne raison de faire autrement. Même en utilisant un pool de mémoire interne, une bonne application free() la mémoire du pool avant sa fermeture. Oui, le système d’exploitation va le nettoyer, mais si le cycle de vie de l’application est modifié par la suite, il serait facile d’oublier que le pool est toujours présent …

Bien entendu, les applications de longue durée doivent scrupuleusement nettoyer ou recycler tout ce qu’elles ont alloué, ou elles finissent par manquer de mémoire.

Vos professeurs soulèvent un point important. Malheureusement, l’utilisation de l’anglais est telle que je ne suis pas absolument sûr de ce qu’ils ont dit. Permettez-moi de répondre à la question en termes de programmes non-jouets qui ont certaines caractéristiques d’utilisation de la mémoire et avec lesquelles j’ai personnellement travaillé.

Certains programmes se comportent bien. Ils allouent de la mémoire en vagues: beaucoup d’atsortingbutions de petite ou moyenne taille suivies de beaucoup de libérations, en cycles répétés. Dans ces programmes, les allocateurs de mémoire classiques fonctionnent plutôt bien. Ils fusionnent des blocs libérés et à la fin d’une vague, la majeure partie de la mémoire libre est en gros morceaux contigus. Ces programmes sont assez rares.

La plupart des programmes se comportent mal. Ils allouent et désallouent la mémoire de manière plus ou moins aléatoire, dans une variété de tailles allant de très petite à très grande, et ils conservent une utilisation élevée des blocs alloués. Dans ces programmes, la capacité à fusionner des blocs est limitée et, avec le temps, ils finissent avec une mémoire hautement fragmentée et relativement non contiguë. Si l’utilisation totale de la mémoire dépasse environ 1,5 Go dans un espace mémoire de 32 bits et qu’il existe des allocations de (disons) 10 Mo ou plus, l’une des grandes allocations finira par échouer. Ces programmes sont communs.

D’autres programmes libèrent peu ou pas de mémoire jusqu’à ce qu’ils s’arrêtent. Ils allouent progressivement la mémoire en cours d’exécution, libérant seulement de petites quantités, puis s’arrêtent, à ce moment-là, toute la mémoire est libérée. Un compilateur est comme ça. Ainsi est une VM. Par exemple, le runtime .NET CLR, lui-même écrit en C ++, ne libère probablement jamais de mémoire. Pourquoi devrait-il?

Et c’est la réponse finale. Dans les cas où le programme utilise suffisamment de mémoire, la gestion de la mémoire à l’aide de malloc et de free n’est pas une réponse suffisante au problème. À moins que vous ayez la chance de faire affaire avec un programme bien comporté, vous devrez concevoir un ou plusieurs allocateurs de mémoire personnalisés qui pré-allouent de gros blocs de mémoire et les sous-atsortingbuent selon une stratégie de votre choix. Vous ne pouvez pas utiliser gratuitement du tout, sauf lorsque le programme s’arrête.

Sans savoir exactement ce que vos professeurs ont dit, pour des programmes véritablement à l’échelle de la production, je sortirais probablement de leur côté.

MODIFIER

J’en ai un pour répondre à certaines critiques. De toute évidence, SO n’est pas un bon endroit pour des postes de ce genre. Juste pour être clair: j’ai environ 30 ans d’expérience dans l’écriture de ce type de logiciel, y compris quelques compilateurs. Je n’ai pas de références académiques, juste mes propres contusions. Je ne peux m’empêcher de sentir que les critiques proviennent de personnes ayant une expérience beaucoup plus étroite et plus courte.

Je répète mon message clé: équilibrer malloc et free n’est pas une solution suffisante pour une allocation de mémoire à grande échelle dans des programmes réels. Le bloc coalescent est normal et achète du temps, mais ce n’est pas suffisant. Vous avez besoin d’allocateurs de mémoire sérieux et intelligents, qui ont tendance à récupérer de la mémoire en morceaux (en utilisant malloc ou autre) et rarement disponibles. C’est probablement le message que les professeurs d’OP avaient en tête, qu’il a mal compris.

Je suis surpris que personne n’ait encore cité The Book :

Ce n’est peut-être pas le cas à terme, car les mémoires peuvent être suffisamment grandes pour qu’il soit impossible de manquer de mémoire pendant la durée de vie de l’ordinateur. Par exemple, il y a environ 3 onds 10 13 microsecondes par an. Par conséquent, si nous étions contre une fois par microseconde, nous aurions besoin d’environ 10 15 cellules de mémoire pour construire une machine pouvant fonctionner pendant 30 ans. Cette quantité de mémoire semble absurde par rapport aux normes actuelles, mais ce n’est pas impossible physiquement. D’un autre côté, les processeurs sont plus rapides et un futur ordinateur peut avoir un grand nombre de processeurs fonctionnant en parallèle sur une seule mémoire. Il est donc possible d’utiliser de la mémoire beaucoup plus rapidement que ce que nous avons supposé.

http://sarabander.github.io/sicp/html/5_002f3.xhtml#FOOT298

Ainsi, de nombreux programmes peuvent très bien fonctionner sans jamais se soucier de libérer de la mémoire.

Je connais un cas où la libération explicite de la mémoire est pire que inutile . Autrement dit, lorsque vous avez besoin de toutes vos données jusqu’à la fin de la durée de vie du processus. En d’autres termes, lorsque vous les libérez, c’est possible juste avant la fin du programme. Comme tout système d’exploitation moderne prend en charge la libération de mémoire lorsqu’un programme meurt, l’appel de free() n’est pas nécessaire dans ce cas. En fait, cela peut ralentir la fin du programme, car il peut être nécessaire d’accéder à plusieurs pages en mémoire.