Dans des langages tels que C, le programmeur est censé insérer des appels pour libérer. Pourquoi le compilateur ne le fait-il pas automatiquement? Les humains le font dans un délai raisonnable (en ignorant les bugs), donc ce n'est pas impossible.
EDIT: Pour une référence future, voici une autre discussion qui présente un exemple intéressant.
compilers
memory-management
garbage-collection
Milton Silva
la source
la source
Réponses:
Parce qu'il est indécidable que le programme utilise à nouveau la mémoire. Cela signifie qu'aucun algorithme ne peut déterminer correctement le moment d'appeler
free()
dans tous les cas, ce qui signifie qu'un compilateur qui aurait tenté de le faire produirait nécessairement certains programmes avec des fuites de mémoire et / ou des programmes continuant à utiliser la mémoire libérée. Même si vous vous êtes assuré que votre compilateur n'a jamais fait le deuxième et que le programmeur insère des appels pourfree()
corriger ces bogues, ilfree()
serait encore plus difficile de savoir quand appeler ce compilateur. Savoir quand appelerfree()
lorsque vous utilisez un compilateur qui n'a pas essayé aider.la source
free()
correctement.Comme David Richerby l’a noté à juste titre, le problème est indécidable en général. La qualité de l'objet est une propriété globale du programme et peut en général dépendre des entrées du programme.
Même le ramassage des ordures dynamique et précis est un problème indécidable! Tous les éboueurs du monde réel utilisent l'accessibilité comme une approximation prudente pour déterminer si un objet attribué sera nécessaire ou non à l'avenir. C'est une bonne approximation, mais c'est quand même une approximation.
Mais ce n'est que vrai en général. L'un des problèmes les plus notoires dans le secteur de l'informatique est "c'est impossible en général, donc nous ne pouvons rien faire". Au contraire, il existe de nombreux cas où il est possible de progresser.
Les implémentations basées sur le comptage de références sont très proches de "le compilateur insérant des désallocations", de sorte qu'il est difficile de faire la différence. Le comptage automatique des références de LLVM (utilisé dans Objective-C et Swift ) est un exemple célèbre.
L'inférence de région et la récupération de place au moment de la compilation sont des domaines de recherche actifs. Cela s'avère beaucoup plus facile dans les langages déclaratifs tels que ML et Mercury , dans lesquels vous ne pouvez pas modifier un objet après sa création.
Maintenant, en ce qui concerne les humains, il existe trois manières principales pour les humains de gérer manuellement les durées de vie des allocations:
la source
C'est un problème d'inachèvement, pas un problème d'indécidabilité
S'il est vrai que le placement optimal des déclarations de désallocation est indécidable, ce n'est tout simplement pas le problème ici. Comme il est indécidable à la fois pour les humains et les compilateurs, il est impossible de toujours choisir en connaissance de cause le placement optimal de la désallocation, qu'il s'agisse d'un processus manuel ou automatique. Et comme personne n'est parfait, un compilateur suffisamment avancé devrait être en mesure de surpasser les performances humaines en devinant les emplacements approximativement optimaux. L’ indécidabilité n’est donc pas la raison pour laquelle nous avons besoin d’énoncés explicites de désallocation .
Il existe des cas dans lesquels des connaissances externes informent le placement des instructions de désallocation. Supprimer ces instructions équivaut alors à supprimer une partie de la logique opérationnelle. Demander à un compilateur de générer automatiquement cette logique revient à lui demander de deviner ce que vous pensez.
Par exemple, supposons que vous écrivez une boucle Read-Evaluate-Print-Loop (REPL) : l'utilisateur tape une commande et votre programme l'exécute. L'utilisateur peut allouer / désallouer de la mémoire en tapant des commandes dans votre REPL. Votre code source spécifierait ce que le REPL doit faire pour chaque commande utilisateur possible, y compris la désaffectation lorsque l'utilisateur tape la commande correspondante.
Mais si le code source C ne fournit pas de commande explicite pour désallocation, le compilateur devra en déduire qu’il doit effectuer le dellocation lorsque l’utilisateur entre la commande appropriée dans le REPL. Cette commande est-elle "désallouer", "libre" ou autre chose? Le compilateur n'a aucun moyen de savoir ce que vous voulez que la commande soit. Même si vous programmez en logique la recherche de ce mot de commande et que le REPL le trouve, le compilateur n'a aucun moyen de savoir qu'il devrait y répondre par une désallocation, à moins que vous ne le lui indiquiez explicitement dans le code source.
tl; dr Le problème est que le code source C ne fournit pas au compilateur des connaissances externes. Le problème n’est pas l’indécidabilité, qu’il s’agisse d’un processus manuel ou automatisé.
la source
Actuellement, aucune des réponses postées n'est totalement correcte.
Certains le font. (Je vais expliquer plus tard.)
Essentiellement, vous pouvez appeler
free()
juste avant la fin du programme. Mais votre question implique implicitement que votre question appellefree()
dès que possible.Le problème de savoir quand appeler
free()
un programme en C dès que la mémoire est inaccessible est indécidable, c'est-à-dire que pour tout algorithme fournissant la réponse en temps fini, il existe un cas qu'il ne couvre pas. Ceci, et de nombreuses autres indécidabilités de programmes arbitraires, peut être prouvé par le problème Halting .Un problème indécidable ne peut pas toujours être résolu en temps fini par un algorithme, qu'il s'agisse d'un compilateur ou d'un humain.
Les humains (tentent de) écrire dans un sous - ensemble de programmes C dont l’ exactitude de la mémoire peut être vérifiée par leur algorithme (eux-mêmes).
Certaines langues accomplissent le n ° 1 en intégrant le n ° 5 dans le compilateur. Ils n'autorisent pas les programmes avec des utilisations arbitraires d'allocation de mémoire, mais plutôt un sous-ensemble décidable d'entre eux. Foth et Rust sont deux exemples de langages ayant une allocation de mémoire plus restrictive que les C
malloc()
, qui peuvent (1) détecter si un programme est écrit en dehors de leur ensemble décidable (2) insérer automatiquement des désallocations.la source
"Les humains le font, alors ce n'est pas impossible" est une erreur bien connue. Nous ne comprenons pas nécessairement (et encore moins contrôlons) les choses que nous créons - la monnaie est un exemple courant. Nous avons tendance à surestimer (parfois considérablement) nos chances de succès en matière de technologie, en particulier lorsque les facteurs humains semblent être absents.
Les performances humaines en programmation informatique sont très médiocres et l’étude de l’informatique (qui manque dans de nombreux programmes de formation professionnelle) permet de comprendre pourquoi ce problème n’a pas une solution simple. Nous pourrions un jour, peut-être pas trop loin, être remplacés par une intelligence artificielle au travail. Même dans ce cas, il n’y aura pas d’algorithme général permettant de désallouer correctement, automatiquement, à tout moment.
la source
L'absence de gestion automatique de la mémoire est une caractéristique du langage.
C n'est pas censé être un outil pour écrire un logiciel facilement. C'est un outil permettant à l'ordinateur de faire ce que vous lui dites. Cela inclut l’allocation et la désallocation de la mémoire au moment de votre choix. C est un langage de bas niveau que vous utilisez lorsque vous souhaitez contrôler l'ordinateur avec précision ou lorsque vous souhaitez effectuer des tâches d'une manière différente de celle attendue par les concepteurs de bibliothèques de langage / standard.
la source
Le problème est principalement un artefact historique, pas une impossibilité de mise en œuvre.
Le code de construction de la plupart des compilateurs C est tel que le compilateur ne voit que chaque fichier source à la fois; il ne voit jamais tout le programme à la fois. Lorsqu'un fichier source appelle une fonction à partir d'un autre fichier source ou d'une bibliothèque, le compilateur voit uniquement le fichier d'en-tête avec le type de retour de la fonction, pas le code réel de la fonction. Cela signifie que lorsqu'une fonction renvoie un pointeur, le compilateur n'a aucun moyen de savoir si la mémoire sur laquelle le pointeur pointe doit être libérée ou non. L'information à décider qui n'est pas montrée au compilateur à ce moment-là. De son côté, un programmeur humain est libre de rechercher le code source de la fonction ou la documentation pour savoir ce qu’il faut faire avec le pointeur.
Si vous examinez des langages de bas niveau plus modernes tels que C ++ 11 ou Rust, vous constaterez qu'ils résolvent principalement le problème en rendant la propriété de la mémoire explicite dans le type du pointeur. En C ++, vous utiliseriez un à la
unique_ptr<T>
place d'un simpleT*
pour conserver la mémoire et launique_ptr<T>
vérifie que la mémoire est libérée lorsque l'objet atteint la fin de la portée, contrairement à l'objet ordinaireT*
. Le programmeur peut transmettre la mémoire de l'ununique_ptr<T>
à l'autre, mais il ne peut jamais y en avoir qu'un quiunique_ptr<T>
pointe vers la mémoire. Il est donc toujours clair à qui appartient la mémoire et quand elle doit être libérée.Le C ++, pour des raisons de compatibilité ascendante, permet toujours la gestion manuelle de la mémoire à l’ancien style et donc la création de bogues ou de moyens de contourner la protection d’un
unique_ptr<T>
. Rust est encore plus strict dans la mesure où il applique des règles de propriété de la mémoire via les erreurs du compilateur.En ce qui concerne l’indécidabilité, le problème d’arrêt, etc., oui, si vous vous en tenez à la sémantique C, il n’est pas possible de décider pour tous les programmes quand la mémoire doit être libérée. Cependant, pour la plupart des programmes actuels, et non des exercices académiques ou des logiciels bogués, il serait absolument possible de décider quand libérer ou non. Après tout, c’est la seule raison pour laquelle l’homme peut déterminer quand libérer ou non.
la source
D'autres réponses ont porté sur la possibilité de procéder à la collecte des ordures, sur certains détails de la procédure et sur certains problèmes.
Un problème qui n’a pas encore été couvert est le retard inévitable dans le ramassage des ordures. En C, quand un programmeur appelle free (), cette mémoire est immédiatement disponible pour une réutilisation. (En théorie du moins!) Ainsi, un programmeur peut libérer sa structure de 100 Mo, allouer une autre structure de 100 Mo une milliseconde plus tard et s’attendre à ce que l’utilisation de la mémoire reste la même.
Ce n'est pas vrai avec la récupération de place. Les systèmes ramassés de déchets ont un certain retard à restituer la mémoire inutilisée au tas, ce qui peut être important. Si votre structure de 100 Mo devient hors de portée et qu'une milliseconde plus tard, votre programme configure une autre structure de 100 Mo, vous pouvez raisonnablement vous attendre à ce que votre système utilise 200 Mo pendant une courte période. Cette "période courte" peut prendre quelques millisecondes ou secondes, selon le système, mais il y a toujours un délai.
Si vous utilisez un PC avec des Go de RAM et de mémoire virtuelle, vous ne le remarquerez probablement jamais. Si vous utilisez un système avec des ressources plus limitées (par exemple, un système intégré ou un téléphone), vous devez prendre cela au sérieux. Ce n’est pas seulement théorique, j’ai personnellement constaté que cela créait des problèmes (comme lors du blocage du type de périphérique) lorsque je travaillais sur un système WinCE utilisant le .NET Compact Framework et évoluant en C #.
la source
La question suppose que la désallocation est quelque chose que le programmeur est supposé déduire des autres parties du code source. Ce n'est pas. "A ce stade du programme, la référence mémoire FOO n'est plus utile", les informations sont connues uniquement dans l'esprit du programmeur jusqu'à ce qu'elles soient codées (dans des langages procéduraux) dans une instruction de désallocation.
Ce n'est théoriquement pas différent de toute autre ligne de code. Pourquoi les compilateurs n'insèrent-ils pas automatiquement "À ce stade du programme, vérifiez l'entrée du registre BAR" ou "si l'appel de fonction renvoie une valeur différente de zéro, quittez le sous-programme en cours" ? Du point de vue du compilateur, la raison en est "l'incomplétude", comme indiqué dans cette réponse . Mais tout programme souffre d'incomplétude quand le programmeur ne lui dit pas tout ce qu'il sait.
Dans la vraie vie, les désallocations sont du gruntwork ou du warmplate; notre cerveau les remplit automatiquement et grogne, et le sentiment que "le compilateur pourrait le faire aussi bien ou mieux" est vrai. En théorie, cependant, ce n'est pas le cas, même si, heureusement, d'autres langues nous offrent plus de choix de théorie.
la source
Ce qui est fait: Il existe un ramasse-miettes et des compilateurs utilisant le comptage de références (Objective-C, Swift). Ceux qui font du comptage de référence ont besoin de l'aide du programmeur en évitant les cycles de référence trop forts.
La vraie réponse au "pourquoi" est que les auteurs de compilateur n'ont pas trouvé de solution suffisamment efficace et rapide pour le rendre utilisable dans un compilateur. Étant donné que les auteurs de compilateur sont généralement très intelligents, vous pouvez en conclure qu'il est très, très difficile de trouver un moyen suffisamment efficace et rapide.
L'une des raisons pour lesquelles c'est très très difficile est bien sûr que c'est indécidable. En informatique, lorsque nous parlons de "décidabilité", nous entendons "prendre la bonne décision". Les programmeurs humains peuvent bien sûr facilement décider où désallouer la mémoire, car ils ne se limitent pas aux décisions correctes . Et ils prennent souvent des décisions qui sont fausses.
la source
Parce que la durée de vie d'un bloc de mémoire est la décision du programmeur, pas celle du compilateur.
C'est ça. C’est la conception de C. Le compilateur ne peut pas savoir quelle était l’intention d’allouer un bloc de mémoire. Les humains peuvent le faire, car ils connaissent le but de chaque bloc de mémoire et savent quand ce but est servi pour le libérer. Cela fait partie de la conception du programme en cours d’écriture.
C étant un langage de bas niveau, les instances de transfert d’un bloc de votre mémoire à un autre processus ou même à un autre processeur sont assez fréquentes. Dans les cas extrêmes, un programmeur peut allouer intentionnellement une partie de la mémoire et ne plus l'utiliser, simplement pour exercer une pression mémoire sur d'autres parties du système. Le compilateur n'a aucun moyen de savoir si le bloc est toujours nécessaire.
la source
En C et dans de nombreux autres langages, il est effectivement possible de faire en sorte que le compilateur fasse l’équivalent dans les cas où cela est clair au moment de la compilation: utilisation de variables à durée automatique (c.-à-d. Variables locales ordinaires) . Il incombe au compilateur de disposer de suffisamment d’espace pour ces variables et de le libérer lorsque leur durée de vie (bien définie) se termine.
Les matrices de longueur variable étant une caractéristique C depuis C99, les objets à durée automatique remplissent en principe pratiquement toutes les fonctions en C que remplissent les objets de durée calculable alloués dynamiquement. En pratique, bien sûr, les implémentations en C peuvent imposer des limites pratiques significatives à l’utilisation des VLA - c’est-à-dire que leur taille peut être limitée du fait de leur affectation en pile - mais il s’agit d’une considération pour la mise en oeuvre, pas pour la conception linguistique.
Les objets dont l'utilisation prévue ne permet pas de leur attribuer une durée automatique sont précisément ceux dont la durée de vie ne peut pas être déterminée au moment de la compilation.
la source