Pourquoi les livres .Net parlent-ils d'allocation de mémoire pile / pile?


36

Il semble que chaque livre .net parle des types de valeur par rapport aux types de référence et en fait un point vers (souvent de manière incorrecte) l’état où chaque type est stocké - le tas ou la pile. Habituellement, cela fait partie des premiers chapitres et est présenté comme un fait très important. Je pense que c'est même couvert lors des examens de certification . Pourquoi la pile contre le tas est-elle aussi importante pour les développeurs (débutants) .Net? Vous allouez des choses et ça marche, non?


11
Certains auteurs ont juste un très mauvais jugement sur ce qui est important d’enseigner aux débutants et ce qui n’est pas du bruit. Dans un livre que j'ai vu récemment, la première mention de modificateurs d'accès incluait déjà la protection interne interne , que je n'avais jamais utilisée depuis 6 ans en C # ...
Timwi

1
Mon hypothèse est que quiconque a écrit la documentation originale .Net pour cette partie en a fait une grosse affaire, et cette documentation est sur laquelle les auteurs ont initialement basé leurs livres, puis elle est restée inchangée.
Greg

Dire que les types de valeur copient tout le texte et que les références ne le font pas serait plus logique et plus facile à comprendre pourquoi utiliser des références, car l'emplacement où ces valeurs sont stockées peut être spécifique à la mise en œuvre et même non pertinent.
Trinidad

Interview culte de la cargaison?
Den

Réponses:


37

Je suis convaincu que la tradition est la principale raison pour laquelle cette information est considérée comme importante . Dans les environnements non gérés, la distinction entre pile et tas est importante et nous devons allouer et supprimer manuellement la mémoire que nous utilisons. Maintenant, la récupération de place prend en charge la gestion, donc ils ignorent ce bit. Je ne pense pas que le message soit vraiment passé: nous ne devons pas nous soucier du type de mémoire utilisé.

Comme Fede l'a souligné, Eric Lippert a des choses très intéressantes à dire à ce sujet: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx .

À la lumière de cette information, vous pouvez ajuster mon premier paragraphe comme suit: "La raison pour laquelle les gens incluent cette information et supposent que c'est important, c'est en raison d'informations inexactes ou incomplètes associées à la nécessité de posséder cette connaissance dans le passé."

Pour ceux qui pensent que c'est toujours important pour des raisons de performance: quelles actions prendriez-vous pour déplacer quelque chose du tas à la pile si vous mesuriez les choses et découvriez que c'était important? Plus probablement, vous trouveriez un moyen complètement différent d’améliorer les performances pour la zone à problèmes.


6
J'ai entendu dire que dans certaines implémentations de framework (compactes sur la Xbox en particulier), il est préférable d'utiliser des structures pendant la période de rendu (le jeu lui-même) pour réduire le garbage collection. Vous utiliseriez toujours des types normaux ailleurs, mais préalloués afin que le CPG ne s'exécute pas pendant la partie. C'est à peu près la seule optimisation que je connaisse de la pile par rapport à la pile que je connaisse dans .NET, et elle est assez spécifique aux besoins du framework compact et des programmes en temps réel.
CodexArcanum

5
Je suis surtout d'accord avec l'argument de la tradition. À un moment donné, de nombreux programmeurs expérimentés ont peut-être programmé dans un langage de bas niveau où ces informations étaient importantes si vous vouliez un code correct et efficace. Cependant, prenons C ++ par exemple, un langage non géré: la spécification officielle ne dit pas que les variables automatiques doivent aller dans la pile, etc. Le standard C ++ traite la pile et le tas comme des détails d'implémentation. +1
Stakx

36

Il semble que chaque livre .NET parle des types de valeur par rapport aux types de référence et en fait un point vers (souvent de manière incorrecte) l’état où chaque type est stocké - le tas ou la pile. Habituellement, cela fait partie des premiers chapitres et est présenté comme un fait très important.

Je suis complètement d'accord; Je le vois tout le temps.

Pourquoi les livres .NET parlent-ils d'allocation de mémoire pile / pile?

Une des raisons est que beaucoup de gens sont venus en C # (ou dans d’autres langages .NET) à partir d’un arrière-plan C ou C ++. Étant donné que ces langues ne vous imposent pas les règles relatives à la durée de stockage, vous devez les connaître et mettre en œuvre votre programme avec soin pour les suivre.

Maintenant, connaître ces règles et les suivre en C ne nécessite pas que vous compreniez "le tas" et "la pile". Mais si vous comprenez comment fonctionnent les structures de données, il est souvent plus facile de comprendre et de suivre les règles.

Lors de la rédaction d'un livre pour débutant, il est naturel qu'un auteur explique les concepts dans le même ordre qu'il les a appris. Ce n'est pas nécessairement l'ordre qui a du sens pour l'utilisateur. J'étais récemment éditeur technique du livre pour débutants C # 4 de Scott Dorman, et l'une des choses que j'ai appréciées à ce sujet est que Scott a choisi un ordre assez judicieux pour les sujets, plutôt que de commencer sur des sujets assez avancés en gestion de la mémoire.

Une autre partie de la raison est que certaines pages de la documentation MSDN mettent fortement l’accent sur les considérations de stockage. Une documentation MSDN particulièrement ancienne, qui traîne toujours depuis le début. Une grande partie de cette documentation contient des erreurs subtiles qui n'ont jamais été supprimées, et vous devez vous rappeler qu'elle a été écrite à un moment donné de l'histoire et pour un public particulier.

Pourquoi la pile contre le tas est-elle importante pour les développeurs (débutants) .NET?

À mon avis, ce n'est pas le cas. Ce qui est beaucoup plus important à comprendre, ce sont des choses comme:

  • Quelle est la différence en sémantique de copie entre un type de référence et un type de valeur?
  • Comment se comporte un paramètre "ref int x"?
  • Pourquoi les types de valeur devraient-ils être immuables?

Etc.

Vous allouez des choses et ça marche, non?

C'est l'idéal.

Maintenant, il y a des situations dans lesquelles cela compte. La collecte des ordures ménagères est géniale et relativement peu coûteuse, mais elle n’est pas gratuite. Copier de petites structures est relativement peu coûteux, mais n’est pas gratuit. Il existe des scénarios de performances réalistes dans lesquels vous devez équilibrer le coût de la pression de collecte par rapport au coût d'une copie excessive. Dans ces cas, il est très utile de bien comprendre la taille, l'emplacement et la durée de vie réelle de toutes les mémoires pertinentes.

De même, il existe des scénarios d'interopérabilité réalistes dans lesquels il est nécessaire de savoir ce qu'il y a sur la pile et ce qu'il y a sur le tas, et ce que le garbage collector pourrait déplacer. C'est pourquoi C # a des fonctionnalités telles que "fixed", "stackalloc" et ainsi de suite.

Mais ce sont tous des scénarios avancés. Idéalement, un programmeur débutant n'a pas besoin de s'inquiéter de ce genre de choses.


2
Merci pour la réponse Eric. Vos récents articles de blog sur le sujet sont ce qui m’a réellement incité à poser la question.
Greg

13

Vous manquez tous le point. La distinction entre pile / tas est importante en raison de sa portée .

struct S { ... }

void f() {
    var x = new S();
    ...
 }

Une fois que x sort de la portée, l’objet créé disparaît catégoriquement . C'est uniquement parce qu'il est alloué sur la pile, pas le tas. Il n’ya rien qui aurait pu entrer dans la partie "..." de la méthode qui puisse changer ce fait. En particulier, les assignations ou les appels de méthodes ne peuvent avoir que des copies de la structure S, et non pas créer de nouvelles références pour lui permettre de rester en vie.

class C { ... }

void f() {
     var x = new C();
     ...
}

Histoire totalement différente! Puisque x est maintenant sur le tas , son objet (c’est-à-dire, l’ objet lui - même , et non une copie de celui-ci) pourrait très bien continuer à vivre après que x soit sorti de sa portée. En fait, la seule façon pour qu'il ne continue pas à vivre est si x en est la seule référence. Si les assignations ou les appels de méthode dans la partie "..." ont créé d'autres références qui sont encore "actives" au moment où x sort de la portée, cet objet continue à exister.

C’est un concept très important et la seule façon de vraiment comprendre «quoi et pourquoi» est de faire la différence entre l’allocation de pile et celle de tas.


Je ne suis pas sûr d'avoir déjà vu cet argument dans des livres avec la discussion pile / tas, mais c'est un bon argument. +1
Greg

2
En raison de la manière dont C # génère des fermetures, le code dans le ...fichier xpeut être converti en un champ d'une classe générée par le compilateur et donc plus longtemps que l'étendue indiquée. Personnellement, je trouve désagréable l’idée d’un levage implicite, mais les concepteurs de langage semblent aller de l’avant (au lieu d’exiger que toute variable qui est soulevée ait quelque chose dans sa déclaration qui le spécifie). Pour garantir l'exactitude du programme, il est souvent nécessaire de prendre en compte toutes les références pouvant exister à un objet. Il est utile de savoir qu'au moment du retour d'une routine, il ne restera aucune copie d'une référence passée.
Supercat

1
En ce qui concerne les 'structures sont sur la pile', la déclaration appropriée est que si un emplacement de stockage est déclaré comme structType foo, l'emplacement de stockage foocontient le contenu de ses champs; Si fooest sur la pile, ses champs le sont aussi. Si fooest sur le tas, ses champs le sont aussi. Si se footrouve dans un Apple II en réseau, ses champs le sont également. En revanche, s’il foos’agissait d’un type de classe, il contiendrait soit nullune référence à un objet. La seule situation dans laquelle fooon puisse dire que le type de classe est celui qui contient les champs de l'objet serait s'il s'agissait du seul champ d'une classe et contenait une référence à lui-même.
Supercat

+1, j'aime votre compréhension et je pense que c'est valable ... Cependant, je ne pense pas que cela justifie pourquoi les livres abordent ce sujet de manière aussi approfondie. On dirait que ce que vous avez expliqué ici pourrait remplacer ces 3 ou 4 chapitres dudit livre et être beaucoup plus utile.
Frank V

1
d'après ce que je sais, les structs ne sont pas obligés et ne vont pas toujours sur la pile.
Sara

5

En ce qui concerne POURQUOI couvrent-ils le sujet, je conviens avec @Kirk que c’est un concept important, que vous devez comprendre. Mieux vous connaîtrez les mécanismes, mieux vous pourrez faire de superbes applications performantes.

Maintenant, Eric Lippert semble être d’accord avec vous pour dire que le sujet n’est pas correctement traité par la plupart des auteurs. Je vous recommande de lire son blog pour bien comprendre ce qui se cache sous le capot.


2
Dans l'article d'Eric, il est clair que tout ce que vous avez besoin de savoir, ce sont les caractéristiques manifestes des types de valeur et de référence, et vous ne devez pas vous attendre à ce que la mise en œuvre reste identique. Je pense que c’est une question délicate de suggérer qu’il existe un moyen efficace de ré-implémenter C # sans pile, mais son propos est correct: cela ne fait pas partie des spécifications du langage. Donc, la seule raison d'utiliser cette explication à laquelle je peux penser, c'est que c'est une allégorie utile pour les programmeurs qui connaissent d'autres langues, en particulier C. Tant qu'ils savent, c'est une allégorie, ce que beaucoup de littérature n'indiquent pas clairement.
Jeremy

5

Eh bien, je pensais que c’était l’intérêt des environnements gérés. J'irais même jusqu'à appeler cela un détail d'implémentation du moteur d'exécution sous-jacent sur lequel vous ne devriez PAS faire d'hypothèse, car cela pourrait changer à tout moment.

Je ne connais pas grand chose à propos de .NET, mais autant que je sache, c'est JITted avant son exécution. Le JIT, par exemple, pourrait effectuer une analyse d'évasion et ce qui ne le serait pas et tout à coup, des objets se trouveraient sur la pile ou simplement dans certains registres. Vous ne pouvez pas savoir cela.

Je suppose que certains livres traitent de ce sujet simplement parce que les auteurs lui attribuent une grande importance ou parce qu'ils supposent que leur public le fait (par exemple, si vous écrivez un "C # pour les programmeurs C ++", vous devriez probablement couvrir le sujet).

Néanmoins, je pense qu'il n'y a pas grand chose de plus à dire que "la mémoire est gérée". Sinon, les gens pourraient tirer de mauvaises conclusions.


2

Vous devez comprendre le fonctionnement de l'allocation de mémoire pour l'utiliser efficacement même si vous n'avez pas à le gérer explicitement. Cela vaut pour presque toutes les abstractions en informatique.


2
Dans les langages gérés, vous devez connaître la différence entre un type de valeur et un type de référence, mais au-delà, il est facile de s’enrouler autour de l’essieu en pensant à la façon dont il est géré sous le capot. Voir ici pour un exemple: stackoverflow.com/questions/4083981/…
Robert Harvey

Je suis d'accord avec Robert

La différence entre tas alloué et pile allouée est exactement ce qui explique la différence entre les types value et reference.
Jeremy


1
Jeremy, la différence entre l'allocation de tas et de pile ne peut pas expliquer le comportement différent entre les types de valeur et les types de référence, car il arrive parfois que les types de valeur et les types de référence se trouvent sur le tas, et pourtant, ils se comportent différemment. La chose la plus importante à comprendre est (par exemple) lorsque vous devez utiliser la méthode de référence par référence pour les types de référence par rapport aux types de valeur. Cela dépend simplement de "est-ce un type de valeur ou un type de référence", pas "est-ce sur le tas".
Tim Goodman

2

Il peut y avoir des cas extrêmes où cela peut faire la différence. L'espace de pile par défaut est 1 meg alors que le tas est de plusieurs gig. Ainsi, si votre solution contient un grand nombre d’objets, vous pouvez manquer d’espace de pile tout en disposant de beaucoup d’espace.

Cependant, pour la plupart, c'est assez académique.


Oui, mais je doute que l’un de ces livres s’efforce d’expliquer que les références elles-mêmes sont stockées dans la pile - qu’importe si vous avez beaucoup de types de référence ou beaucoup de types de valeur, vous pouvez toujours avoir un débordement de pile.
Jeremy

0

Comme vous le dites, C # est supposé résumer la gestion de la mémoire, et l’allocation tas-pile est une information de mise en œuvre que le développeur ne devrait en principe pas connaître.

Le problème est que certaines choses sont vraiment difficiles à expliquer de manière intuitive sans se référer à ces détails d'implémentation. Essayez d'expliquer le comportement observable lorsque vous modifiez des types de valeurs mutables - il est presque impossible de le faire sans faire référence à la distinction pile / tas. Ou essayez d’expliquer pourquoi vous avez même des types de valeur dans la langue et quand vous les utiliseriez? Vous devez comprendre la distinction afin de donner un sens à la langue.

Notez que les livres sur Python ou JavaScript ne font pas une grosse affaire s’ils le mentionnent même. En effet, tout est soit alloué, soit immuable, ce qui signifie que les différentes sémantiques de la copie ne sont jamais prises en compte. Dans ces langages, l’abstraction de la mémoire fonctionne; en C #, elle fuit.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.