Qu'est-ce que et où sont la pile et le tas?

8105

Les livres de langage de programmation expliquent que les types de valeur sont créés sur la pile et les types de référence sont créés sur le tas , sans expliquer ce que sont ces deux choses. Je n'ai pas lu d'explication claire à ce sujet. Je comprends ce qu'est une pile . Mais,

  • Où et que sont-ils (physiquement dans la mémoire d'un vrai ordinateur)?
  • Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou l'exécution du langage?
  • Quelle est leur portée?
  • Qu'est-ce qui détermine la taille de chacun d'eux?
  • Qu'est-ce qui rend un plus rapide?
mattshane
la source
175
une très bonne explication peut être trouvée ici Quelle est la différence entre une pile et un tas?
Songo
12
Aussi (vraiment) bien: codeproject.com/Articles/76153/… (la partie pile / tas)
Ben
3
En relation, voir Stack Clash . Les corrections de Stack Clash ont affecté certains aspects des variables système et des comportements comme rlimit_stack. Voir également le numéro 1463241 de
2017
3
@mattshane Les définitions de pile et de tas ne dépendent pas du tout des types de valeur et de référence. En d'autres termes, la pile et le tas peuvent être entièrement définis même si les types valeur et référence n'ont jamais existé. De plus, lors de la compréhension des types de valeur et de référence, la pile n'est qu'un détail d'implémentation. Per Eric Lippert: La pile est un détail d'implémentation, première partie .
Matthew

Réponses:

5968

La pile est la mémoire mise de côté comme espace de travail pour un thread d'exécution. Lorsqu'une fonction est appelée, un bloc est réservé en haut de la pile pour les variables locales et certaines données de comptabilité. Lorsque cette fonction revient, le bloc devient inutilisé et peut être utilisé la prochaine fois qu'une fonction est appelée. La pile est toujours réservée dans un ordre LIFO (dernier entré premier sorti); le bloc le plus récemment réservé est toujours le bloc suivant à libérer. Cela rend très simple le suivi de la pile; libérer un bloc de la pile n'est rien d'autre que régler un pointeur.

Le tas est de la mémoire réservée à l'allocation dynamique. Contrairement à la pile, il n'y a pas de modèle appliqué à l'allocation et à la désallocation des blocs du tas; vous pouvez allouer un bloc à tout moment et le libérer à tout moment. Cela rend beaucoup plus complexe le suivi des parties du tas allouées ou libres à un moment donné; de nombreux allocateurs de segments de mémoire personnalisés sont disponibles pour ajuster les performances des segments de mémoire pour différents modèles d'utilisation.

Chaque thread obtient une pile, alors qu'il n'y a généralement qu'un seul segment pour l'application (bien qu'il ne soit pas rare d'avoir plusieurs segments pour différents types d'allocation).

Pour répondre directement à vos questions:

Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou le langage d'exécution?

Le système d'exploitation alloue la pile pour chaque thread au niveau du système lorsque le thread est créé. En règle générale, le système d'exploitation est appelé par le langage d'exécution pour allouer le segment de mémoire à l'application.

Quelle est leur portée?

La pile est attachée à un thread, donc lorsque le thread quitte la pile est récupérée. Le segment de mémoire est généralement alloué au démarrage de l'application par le runtime et est récupéré lorsque l'application (techniquement processus) se ferme.

Qu'est-ce qui détermine la taille de chacun d'eux?

La taille de la pile est définie lors de la création d'un thread. La taille du segment de mémoire est définie au démarrage de l'application, mais peut augmenter en fonction de l'espace nécessaire (l'allocateur demande plus de mémoire au système d'exploitation).

Qu'est-ce qui rend un plus rapide?

La pile est plus rapide car le modèle d'accès rend banale l'allocation et la désallocation de mémoire (un pointeur / entier est simplement incrémenté ou décrémenté), tandis que le tas a une comptabilité beaucoup plus complexe impliquée dans une allocation ou une désallocation. De plus, chaque octet de la pile a tendance à être réutilisé très fréquemment, ce qui signifie qu'il a tendance à être mappé au cache du processeur, ce qui le rend très rapide. Un autre impact sur les performances du tas est que le tas, étant principalement une ressource globale, doit généralement être sécurisé pour plusieurs threads, c'est-à-dire que chaque allocation et désallocation doit être - généralement - synchronisée avec "tous" les autres accès de tas du programme.

Une démonstration claire:
Source de l'image: vikashazrati.wordpress.com

Jeff Hill
la source
75
Bonne réponse - mais je pense que vous devriez ajouter que si la pile est allouée par l'OS au démarrage du processus (en supposant l'existence d'un OS), elle est maintenue en ligne par le programme. C'est une autre raison pour laquelle la pile est plus rapide, ainsi - les opérations push et pop sont généralement une instruction machine, et les machines modernes peuvent faire au moins 3 d'entre elles en un cycle, alors que l'allocation ou la libération de tas implique l'appel au code OS.
sqykly
276
Je suis vraiment confus par le schéma à la fin. Je pensais l'avoir compris jusqu'à ce que je voie cette image.
Sina Madani
10
@Anarelle le processeur exécute les instructions avec ou sans système d'exploitation. Un exemple qui me tient à cœur est le SNES, qui n'avait pas d'appels API, pas d'OS tel que nous le connaissons aujourd'hui - mais il avait une pile. L'allocation sur une pile est l'addition et la soustraction sur ces systèmes et c'est bien pour les variables détruites lorsqu'elles sont sautées en revenant de la fonction qui les a créées, mais faites une comparaison avec, disons, un constructeur, dont le résultat ne peut pas simplement être jeté. Pour cela, nous avons besoin du tas, qui n'est pas lié à l'appel et au retour. La plupart des systèmes d'exploitation ont des API un tas, aucune raison de le faire vous-même
sqykly
2
msgstr "la pile est la mémoire réservée comme espace de travail". Cool. Mais où est-il réellement «mis de côté» en termes de structure de mémoire Java ?? S'agit-il de mémoire de tas / de mémoire non-tas / Autre (structure de mémoire Java selon betsol.com/2017/06/… )
Jatin Shashoo
4
@JatinShashoo Java runtime, en tant qu'interprète de bytecode, ajoute un niveau de virtualisation supplémentaire, donc ce que vous avez mentionné n'est que le point de vue de l'application Java. Du point de vue du système d'exploitation, tout cela n'est qu'un tas, où le processus d'exécution Java alloue une partie de son espace en tant que mémoire «non-tas» pour le bytecode traité. Le reste de ce tas au niveau du système d'exploitation est utilisé comme tas au niveau de l'application, où les données de l'objet sont stockées.
kbec
2350

Empiler:

  • Stocké dans la RAM de l'ordinateur comme le tas.
  • Les variables créées sur la pile seront hors de portée et seront automatiquement désallouées.
  • Beaucoup plus rapide à allouer par rapport aux variables du tas.
  • Implémenté avec une structure de données de pile réelle.
  • Stocke les données locales, les adresses de retour, utilisées pour le passage des paramètres.
  • Peut avoir un débordement de pile quand une trop grande partie de la pile est utilisée (principalement à partir d'une récursion infinie ou trop profonde, de très grandes allocations).
  • Les données créées sur la pile peuvent être utilisées sans pointeurs.
  • Vous utiliseriez la pile si vous savez exactement combien de données vous devez allouer avant la compilation et qu'elle n'est pas trop grande.
  • A généralement une taille maximale déjà déterminée au démarrage de votre programme.

Tas:

  • Stocké dans la RAM de l'ordinateur comme la pile.
  • En C ++, les variables du tas doivent être détruites manuellement et ne tombent jamais hors de portée. Les données est libéré avec delete, delete[], ou free.
  • Allocation plus lente par rapport aux variables de la pile.
  • Utilisé à la demande pour allouer un bloc de données à utiliser par le programme.
  • Peut être fragmenté lorsqu'il y a beaucoup d'allocations et de désallocations.
  • En C ++ ou C, les données créées sur le tas seront pointées par des pointeurs et allouées avec newou mallocrespectivement.
  • Peut avoir des échecs d'allocation si une trop grande quantité de tampon est demandée pour être allouée.
  • Vous utiliseriez le tas si vous ne savez pas exactement combien de données vous aurez besoin au moment de l'exécution ou si vous avez besoin d'allouer beaucoup de données.
  • Responsable des fuites de mémoire.

Exemple:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;
Brian R. Bondy
la source
31
Le pointeur pBuffer et la valeur de b sont situés sur la pile, et sont généralement attribués à l'entrée de la fonction. Selon le compilateur, un tampon peut également être alloué à l'entrée de la fonction.
Andy
36
C'est une idée fausse que le Clangage, tel que défini par la C99norme de langage (disponible sur open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), nécessite une "pile". En fait, le mot «pile» n'apparaît même pas dans la norme. Cela répond que les déclarations sur Cl'utilisation de la pile par wrt / to sont vraies en général, mais ne sont en aucun cas requises par le langage. Voir knosof.co.uk/cbook/cbook.html pour plus d'informations, et en particulier comment Cest mis en œuvre sur des architectures impaires telles que en.wikipedia.org/wiki/Burroughs_large_systems
johne
55
@Brian Vous devez expliquer pourquoi le tampon [] et le pointeur pBuffer sont créés sur la pile et pourquoi les données de pBuffer sont créées sur le tas. Je pense que certains ppl pourraient être confus par votre réponse car ils pourraient penser que le programme demande spécifiquement que la mémoire soit allouée sur la pile vs le tas, mais ce n'est pas le cas. Est-ce parce que Buffer est un type de valeur alors que pBuffer est un type de référence?
Howiecamp
9
@Remover: Aucun pointeur ne détient une adresse et il peut pointer vers quelque chose sur le tas ou la pile également. new, malloc et certaines autres fonctions similaires à malloc allocate sur le tas et renvoient l'adresse de la mémoire allouée. Pourquoi voudriez-vous allouer sur le tas? Pour que votre mémoire ne soit pas hors de portée et libérée tant que vous ne le souhaitez pas.
Brian R. Bondy
35
"Responsable des fuites de mémoire" - Les tas ne sont pas responsables des fuites de mémoire! Les codeurs paresseux / oublieux / ex-java / codeurs qui ne donnent pas de conneries le sont!
Laz
1372

Le point le plus important est que le tas et la pile sont des termes génériques pour les façons dont la mémoire peut être allouée. Ils peuvent être mis en œuvre de différentes manières et les termes s'appliquent aux concepts de base.

  • Dans une pile d'objets, les objets sont placés les uns sur les autres dans l'ordre où ils ont été placés, et vous ne pouvez retirer que celui du haut (sans renverser le tout).

    Pile comme une pile de papiers

    La simplicité d'une pile est que vous n'avez pas besoin de maintenir une table contenant un enregistrement de chaque section de mémoire allouée; les seules informations d'état dont vous avez besoin sont un seul pointeur vers la fin de la pile. Pour allouer et désallouer, il vous suffit d'incrémenter et de décrémenter ce pointeur unique. Remarque: une pile peut parfois être implémentée pour démarrer en haut d'une section de mémoire et s'étendre vers le bas plutôt que de croître vers le haut.

  • Dans un tas, il n'y a pas d'ordre particulier à la façon dont les éléments sont placés. Vous pouvez atteindre et supprimer des articles dans n'importe quel ordre, car il n'y a pas d'élément «supérieur» clair.

    Amas comme un tas d'allorts de réglisse

    L'allocation de segments nécessite de conserver un enregistrement complet de la mémoire allouée et de ce qui ne l'est pas, ainsi qu'une maintenance de surcharge pour réduire la fragmentation, trouver des segments de mémoire contigus suffisamment grands pour s'adapter à la taille demandée, etc. La mémoire peut être désallouée à tout moment en laissant de l'espace libre. Parfois, un allocateur de mémoire effectuera des tâches de maintenance telles que la défragmentation de la mémoire en déplaçant la mémoire allouée ou le ramasse-miettes - identifiant au moment de l'exécution lorsque la mémoire n'est plus dans la portée et la désallouant.

Ces images devraient décrire assez bien les deux façons d'allouer et de libérer de la mémoire dans une pile et un tas. Miam!

  • Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou le langage d'exécution?

    Comme mentionné, tas et pile sont des termes généraux et peuvent être implémentés de plusieurs façons. Les programmes informatiques ont généralement une pile appelée pile d' appels qui stocke des informations pertinentes pour la fonction actuelle, comme un pointeur vers la fonction à partir de laquelle elle a été appelée et toutes les variables locales. Étant donné que les fonctions appellent d'autres fonctions puis reviennent, la pile s'agrandit et se rétrécit pour contenir les informations des fonctions situées plus bas dans la pile d'appels. Un programme n'a pas vraiment de contrôle d'exécution sur celui-ci; il est déterminé par le langage de programmation, le système d'exploitation et même l'architecture du système.

    Un tas est un terme général utilisé pour toute mémoire allouée de manière dynamique et aléatoire; c'est-à-dire hors service. La mémoire est généralement allouée par le système d'exploitation, l'application appelant des fonctions API pour effectuer cette allocation. La gestion de la mémoire allouée dynamiquement nécessite un peu de surcharge, généralement gérée par le code d'exécution du langage de programmation ou de l'environnement utilisé.

  • Quelle est leur portée?

    La pile d'appels est un concept tellement bas qu'il ne se rapporte pas à la «portée» au sens de la programmation. Si vous démontez du code, vous verrez des références de style de pointeur relatives à des parties de la pile, mais en ce qui concerne un langage de niveau supérieur, le langage impose ses propres règles de portée. Cependant, un aspect important d'une pile est qu'une fois qu'une fonction revient, tout ce qui est local à cette fonction est immédiatement libéré de la pile. Cela fonctionne comme vous vous attendez à ce qu'il fonctionne, compte tenu du fonctionnement de vos langages de programmation. Dans un tas, il est également difficile à définir. La portée est tout ce qui est exposé par le système d'exploitation, mais votre langage de programmation ajoute probablement ses règles sur ce qu'est une «portée» dans votre application. L'architecture du processeur et le système d'exploitation utilisent l'adressage virtuel, que le processeur traduit en adresses physiques et il y a des défauts de page, etc. Ils gardent une trace des pages qui appartiennent à quelles applications. Cependant, vous n'avez jamais vraiment à vous en préoccuper, car vous utilisez simplement la méthode utilisée par votre langage de programmation pour allouer et libérer de la mémoire et vérifier les erreurs (si l'allocation / la libération échoue pour une raison quelconque).

  • Qu'est-ce qui détermine la taille de chacun d'eux?

    Encore une fois, cela dépend du langage, du compilateur, du système d'exploitation et de l'architecture. Une pile est généralement pré-allouée car, par définition, elle doit être une mémoire contiguë. Le compilateur de langue ou le système d'exploitation déterminent sa taille. Vous ne stockez pas d'énormes blocs de données sur la pile, elle sera donc suffisamment grande pour ne jamais être utilisée à fond, sauf en cas de récursion sans fin indésirable (d'où le "débordement de pile") ou d'autres décisions de programmation inhabituelles.

    Un tas est un terme général pour tout ce qui peut être alloué dynamiquement. Selon la façon dont vous le regardez, il change constamment de taille. Dans les processeurs et les systèmes d'exploitation modernes, la façon exacte dont il fonctionne est de toute façon très abstraite, vous n'avez donc normalement pas à vous soucier de la façon dont il fonctionne en profondeur, sauf que (dans les langues où cela vous permet), vous ne devez pas utiliser de mémoire qui vous n'avez pas encore alloué ou de mémoire que vous avez libérée.

  • Qu'est-ce qui rend un plus rapide?

    La pile est plus rapide car toute la mémoire libre est toujours contiguë. Aucune liste ne doit être conservée de tous les segments de mémoire libre, juste un seul pointeur vers le haut actuel de la pile. Les compilateurs stockent généralement ce pointeur dans un registre spécial et rapide à cet effet. De plus, les opérations ultérieures sur une pile sont généralement concentrées dans des zones de mémoire très proches, ce qui, à un niveau très bas, est bon pour l'optimisation par les caches en processeur du processeur.

thomasrutter
la source
20
David Je ne suis pas d'accord pour dire que c'est une bonne image ou que "pile déroulante" est un bon terme pour illustrer le concept. Lorsque vous ajoutez quelque chose à une pile, les autres contenus de la pile ne sont pas poussés vers le bas, ils restent où ils sont.
thomasrutter
8
Cette réponse comprend une grosse erreur. Les variables statiques ne sont pas allouées sur la pile. Voir ma réponse [lien] stackoverflow.com/a/13326916/1763801 pour des éclaircissements. vous assimilez des variables "automatiques" à des variables "statiques", mais elles ne sont pas du tout les mêmes
davec
13
Plus précisément, vous dites que les "variables locales allouées statiquement" sont allouées sur la pile. En fait, ils sont alloués dans le segment de données. Seules les variables allouées automatiquement (qui incluent la plupart des variables locales, mais pas toutes, ainsi que des éléments comme les paramètres de fonction transmis par valeur plutôt que par référence) sont allouées sur la pile.
davec
9
Je viens de réaliser que vous avez raison - en C, l'allocation statique est sa propre chose distincte plutôt qu'un terme pour tout ce qui n'est pas dynamique . J'ai édité ma réponse, merci.
thomasrutter
5
Ce n'est pas seulement C. Java, Pascal, Python et bien d'autres ont tous les notions d'allocation statique versus automatique versus dynamique. Dire «allocation statique» signifie la même chose un peu partout. Dans aucune langue, l'allocation statique ne signifie «non dynamique». Vous voulez le terme d'allocation "automatique" pour ce que vous décrivez (c'est-à-dire les choses sur la pile).
davec
727

(J'ai déplacé cette réponse d'une autre question qui était plus ou moins une dupe de celle-ci.)

La réponse à votre question est spécifique à l'implémentation et peut varier selon les compilateurs et les architectures de processeur. Cependant, voici une explication simplifiée.

  • La pile et le tas sont des zones de mémoire allouées à partir du système d'exploitation sous-jacent (souvent de la mémoire virtuelle qui est mappée à la mémoire physique à la demande).
  • Dans un environnement multi-thread, chaque thread aura sa propre pile complètement indépendante mais ils partageront le tas. L'accès simultané doit être contrôlé sur le tas et n'est pas possible sur la pile.

Le tas

  • Le tas contient une liste liée de blocs utilisés et libres. Les nouvelles allocations sur le tas (par newou malloc) sont satisfaites en créant un bloc approprié à partir de l'un des blocs libres. Cela nécessite de mettre à jour la liste des blocs sur le tas. Ces méta-informations sur les blocs sur le tas sont également stockées sur le tas souvent dans une petite zone juste en face de chaque bloc.
  • Au fur et à mesure que le tas grandit, de nouveaux blocs sont souvent alloués à partir d'adresses inférieures vers des adresses supérieures. Ainsi, vous pouvez considérer le tas comme un tas de blocs de mémoire dont la taille augmente à mesure que la mémoire est allouée. Si le tas est trop petit pour une allocation, la taille peut souvent être augmentée en acquérant plus de mémoire à partir du système d'exploitation sous-jacent.
  • L'allocation et la désallocation de nombreux petits blocs peuvent laisser le tas dans un état où il y a beaucoup de petits blocs libres intercalés entre les blocs utilisés. Une demande d'allocation d'un grand bloc peut échouer car aucun des blocs libres n'est suffisamment grand pour satisfaire la demande d'allocation même si la taille combinée des blocs libres peut être suffisamment grande. C'est ce qu'on appelle la fragmentation en tas .
  • Lorsqu'un bloc utilisé adjacent à un bloc libre est désalloué, le nouveau bloc libre peut être fusionné avec le bloc libre adjacent pour créer un bloc libre plus grand réduisant efficacement la fragmentation du tas.

Le tas

La pile

  • La pile fonctionne souvent en tandem étroit avec un registre spécial sur le processeur nommé pointeur de pile . Initialement, le pointeur de pile pointe vers le haut de la pile (l'adresse la plus élevée de la pile).
  • Le CPU a des instructions spéciales pour pousser les valeurs sur la pile et les extraire de la pile. Chaque poussée stocke la valeur à l'emplacement actuel du pointeur de pile et diminue le pointeur de pile. Un pop récupère la valeur pointée par le pointeur de pile, puis augmente le pointeur de pile (ne soyez pas dérouté par le fait que l' ajout d' une valeur à la pile diminue le pointeur de pile et la suppression d' une valeur l' augmente . N'oubliez pas que la pile s'agrandit jusqu'à le fond). Les valeurs stockées et récupérées sont les valeurs des registres CPU.
  • Lorsqu'une fonction est appelée, la CPU utilise des instructions spéciales qui poussent le pointeur d'instruction courant , c'est-à-dire l'adresse du code s'exécutant sur la pile. La CPU passe ensuite à la fonction en définissant le pointeur d'instruction sur l'adresse de la fonction appelée. Plus tard, lorsque la fonction revient, l'ancien pointeur d'instruction est extrait de la pile et l'exécution reprend au code juste après l'appel à la fonction.
  • Lorsqu'une fonction est entrée, le pointeur de pile est diminué pour allouer plus d'espace sur la pile aux variables locales (automatiques). Si la fonction a une variable locale de 32 bits, quatre octets sont mis de côté sur la pile. Lorsque la fonction revient, le pointeur de pile est reculé pour libérer la zone allouée.
  • Si une fonction a des paramètres, ceux-ci sont poussés sur la pile avant l'appel à la fonction. Le code de la fonction peut ensuite remonter la pile à partir du pointeur de pile actuel pour localiser ces valeurs.
  • Les appels de fonction d'imbrication fonctionnent comme un charme. Chaque nouvel appel allouera des paramètres de fonction, l'adresse de retour et l'espace pour les variables locales et ces enregistrements d'activation peuvent être empilés pour les appels imbriqués et se dérouleront correctement lorsque les fonctions reviendront.
  • Comme la pile est un bloc de mémoire limité, vous pouvez provoquer un débordement de pile en appelant trop de fonctions imbriquées et / ou en allouant trop d'espace pour les variables locales. Souvent, la zone de mémoire utilisée pour la pile est configurée de telle manière que l'écriture sous le bas (l'adresse la plus basse) de la pile déclenche un piège ou une exception dans le CPU. Cette condition exceptionnelle peut ensuite être détectée par le runtime et convertie en une sorte d'exception de dépassement de pile.

La pile

Une fonction peut-elle être allouée sur le tas au lieu d'une pile?

Non, les enregistrements d'activation pour les fonctions (c'est-à-dire les variables locales ou automatiques) sont alloués sur la pile qui est utilisée non seulement pour stocker ces variables, mais aussi pour garder une trace des appels de fonction imbriqués.

La façon dont le tas est géré dépend vraiment de l'environnement d'exécution. C utilise mallocet utilise C ++ new, mais de nombreux autres langages ont une récupération de place.

Cependant, la pile est une fonctionnalité de bas niveau étroitement liée à l'architecture du processeur. Agrandir le tas lorsqu'il n'y a pas assez d'espace n'est pas trop difficile car il peut être implémenté dans l'appel de bibliothèque qui gère le tas. Cependant, l'agrandissement de la pile est souvent impossible car le débordement de pile n'est découvert que lorsqu'il est trop tard; et la fermeture du thread d'exécution est la seule option viable.

Martin Liversage
la source
35
@Martin - Une très bonne réponse / explication que la réponse plus abstraite acceptée. Un exemple de programme d'assemblage montrant des pointeurs / registres de pile utilisés vis-à-vis d'appels de fonction serait plus illustratif.
Bikal Lem
3
Chaque type de référence est une composition de types de valeurs (int, chaîne, etc.). Comme il est dit, les types de valeurs sont stockés dans la pile que comment cela fonctionne-t-il lorsqu'ils font partie du type de référence.
Nps
15
Cette réponse était la meilleure à mon avis, car elle m'a aidé à comprendre ce qu'est réellement une déclaration de retour et comment elle se rapporte à cette "adresse de retour" que je rencontre de temps en temps, ce que signifie pousser une fonction sur la pile, et pourquoi les fonctions sont poussées sur des piles. Très bonne réponse!
Alex
3
C'est le meilleur à mon avis, à savoir pour mentionner que le tas / pile est très spécifique à l'implémentation. Les autres réponses supposent beaucoup de choses sur la langue et l'environnement / OS. +1
Qix - MONICA A ÉTÉ BRUÉE
2
Que voulez-vous dire "Le code de la fonction est alors capable de remonter la pile à partir du pointeur de pile actuel pour localiser ces valeurs." ? Pouvez-vous nous en dire plus sur ce sujet
Koray Tugay
404

Dans le code C # suivant

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

Voici comment la mémoire est gérée

Image des variables sur la pile

Local Variablesqui n'ont besoin de durer aussi longtemps que l'invocation de la fonction va dans la pile. Le tas est utilisé pour les variables dont nous ne connaissons pas vraiment la durée de vie mais nous nous attendons à ce qu'elles durent un certain temps. Dans la plupart des langages, il est essentiel de savoir au moment de la compilation la taille d'une variable si nous voulons la stocker sur la pile.

Les objets (dont la taille varie au fur et à mesure que nous les mettons à jour) vont sur le tas car nous ne savons pas au moment de la création combien de temps ils vont durer. Dans de nombreuses langues, le tas est récupéré pour trouver des objets (tels que l'objet cls1) qui n'ont plus de références.

En Java, la plupart des objets vont directement dans le tas. Dans des langages comme C / C ++, les structures et les classes peuvent souvent rester sur la pile lorsque vous ne traitez pas avec des pointeurs.

Plus d'informations peuvent être trouvées ici:

La différence entre la pile et l'allocation de mémoire en tas «timmurphy.org

et ici:

Création d'objets sur la pile et le tas

Cet article est la source de l'image ci-dessus: Six concepts .NET importants: pile, segment de mémoire, types de valeur, types de référence, boxing et unboxing - CodeProject

mais sachez qu'il peut contenir des inexactitudes.

Snowcrash
la source
15
Ceci est une erreur. i et cls ne sont pas des variables "statiques". elles sont appelées variables "locales" ou "automatiques". C'est une distinction très importante. Voir [lien] stackoverflow.com/a/13326916/1763801 pour des éclaircissements
davec
9
Je n'ai pas dit qu'il s'agissait de variables statiques . J'ai dit que int et cls1 sont des éléments statiques . Leur mémoire est allouée statiquement et donc ils vont sur la pile. Ceci contraste avec un objet qui nécessite une allocation dynamique de mémoire qui va donc sur le tas.
Snowcrash
12
Je cite "Les objets statiques ... allez sur la pile". C'est carrément faux. Les éléments statiques vont dans le segment de données, les éléments automatiques vont sur la pile.
davec
14
Quiconque a écrit cet article de projet de code ne sait pas de quoi il parle. Par exemple, il dit que "les primitifs ont besoin d'une mémoire de type statique", ce qui est complètement faux. Rien ne vous empêche d'allouer dynamiquement des primitives dans le tas, écrivez simplement quelque chose comme "int array [] = new int [num]" et le tour est joué, primitives allouées dynamiquement dans .NET. Ce n'est là qu'une des nombreuses inexactitudes.
davec
8
J'ai édité votre message parce que vous avez fait de graves erreurs techniques sur ce qui se passe dans la pile et le tas.
Tom Leys
209

La pile Lorsque vous appelez une fonction, les arguments de cette fonction ainsi que d'autres frais généraux sont placés sur la pile. Certaines informations (comme où aller au retour) y sont également stockées. Lorsque vous déclarez une variable à l'intérieur de votre fonction, cette variable est également allouée sur la pile.

La désallocation de la pile est assez simple car vous désallouez toujours dans l'ordre inverse dans lequel vous l'allouez. Stack stuff est ajouté lorsque vous entrez dans les fonctions, les données correspondantes sont supprimées lorsque vous les quittez. Cela signifie que vous avez tendance à rester dans une petite région de la pile, sauf si vous appelez de nombreuses fonctions qui appellent de nombreuses autres fonctions (ou créez une solution récursive).

Le tas Le tas est un nom générique pour l'endroit où vous placez les données que vous créez à la volée. Si vous ne savez pas combien de vaisseaux spatiaux votre programme va créer, vous utiliserez probablement le nouvel opérateur (ou malloc ou équivalent) pour créer chaque vaisseau spatial. Cette allocation va durer un certain temps, il est donc probable que nous libérions les choses dans un ordre différent de celui que nous avons créé.

Ainsi, le tas est beaucoup plus complexe, car il finit par être des régions de mémoire qui sont inutilisées entrelacées avec des morceaux qui sont - la mémoire est fragmentée. Trouver de la mémoire libre de la taille dont vous avez besoin est un problème difficile. C'est pourquoi le tas doit être évité (bien qu'il soit encore souvent utilisé).

Implémentation L' implémentation de la pile et du tas se fait généralement au niveau de l'exécution / du système d'exploitation. Souvent, les jeux et autres applications dont les performances sont essentielles créent leurs propres solutions de mémoire qui récupèrent une grande partie de la mémoire du tas, puis la répartissent en interne pour éviter de dépendre du système d'exploitation pour la mémoire.

Cela n'est pratique que si votre utilisation de la mémoire est assez différente de la norme - c'est-à-dire pour les jeux où vous chargez un niveau en une seule opération énorme et pouvez tout jeter dans une autre opération énorme.

Emplacement physique en mémoire Ceci est moins pertinent que vous ne le pensez en raison d'une technologie appelée mémoire virtuelle qui fait penser à votre programme que vous avez accès à une certaine adresse où les données physiques sont ailleurs (même sur le disque dur!). Les adresses que vous obtenez pour la pile sont en ordre croissant à mesure que votre arbre d'appels s'approfondit. Les adresses du tas sont imprévisibles (c'est-à-dire spécifiques à l'implantation) et franchement pas importantes.

Tom Leys
la source
16
Une recommandation pour éviter d'utiliser le tas est assez forte. Les systèmes modernes ont de bons gestionnaires de tas, et les langages dynamiques modernes utilisent largement le tas (sans que le programmeur s'en soucie vraiment). Je dirais utiliser le tas, mais avec un allocateur manuel, n'oubliez pas de libérer!
Greg Hewgill
2
Si vous pouvez utiliser la pile ou le tas, utilisez la pile. Si vous ne pouvez pas utiliser la pile, vraiment pas le choix. J'utilise beaucoup les deux, et bien sûr, en utilisant std :: vector ou similaire frappe le tas. Pour un novice, vous évitez le tas car la pile est tout simplement si simple !!
Tom Leys
Si votre langage n'implémente pas la récupération de place, les pointeurs intelligents (objets alloués séparément qui entourent un pointeur qui font référence au comptage des morceaux de mémoire alloués dynamiquement) sont étroitement liés à la récupération de place et sont un moyen décent de gérer le tas dans un coffre-fort. et sans fuite. Ils sont mis en œuvre dans différents cadres, mais ne sont pas difficiles à mettre en œuvre pour vos propres programmes.
BenPen
1
"C'est pourquoi le tas doit être évité (bien qu'il soit encore souvent utilisé)." Je ne sais pas ce que cela signifie pratiquement, d'autant plus que la mémoire est gérée différemment dans de nombreuses langues de haut niveau. Comme cette question est étiquetée indépendamment de la langue, je dirais que ce commentaire / cette ligne est mal placé et ne s'applique pas.
LintfordPickle
2
Bon point @JonnoHampson - Pendant que vous faites valoir un argument valable, je dirais que si vous travaillez dans un "langage de haut niveau" avec un GC, vous ne vous souciez probablement pas du tout des mécanismes d'allocation de mémoire - et donc ne le faites pas se soucient même de la pile et du tas.
Tom Leys
194

Pour clarifier, cette réponse contient des informations incorrectes ( Thomas a corrigé sa réponse après les commentaires, cool :)). D'autres réponses évitent simplement d'expliquer ce que signifie l'allocation statique. J'expliquerai donc les trois principales formes d'allocation et comment elles sont généralement liées au segment de tas, de pile et de données ci-dessous. Je vais également montrer quelques exemples en C / C ++ et en Python pour aider les gens à comprendre.

Les variables "statiques" (AKA allouées statiquement) ne sont pas allouées sur la pile. Ne le supposez pas - beaucoup de gens le font seulement parce que "statique" ressemble beaucoup à "pile". Ils n'existent en fait ni dans la pile ni dans le tas. Ils font partie de ce qu'on appelle le segment de données .

Cependant, il est généralement préférable de considérer « portée » et « durée de vie » plutôt que «pile» et «tas».

La portée fait référence aux parties du code qui peuvent accéder à une variable. En général, nous pensons à la portée locale (accessible uniquement par la fonction actuelle) par rapport à la portée globale (accessible n'importe où), bien que la portée puisse devenir beaucoup plus complexe.

La durée de vie fait référence à quand une variable est allouée et désallouée pendant l'exécution du programme. Habituellement, nous pensons à l'allocation statique (la variable persistera pendant toute la durée du programme, ce qui la rend utile pour stocker les mêmes informations sur plusieurs appels de fonction) à l'allocation automatique (la variable ne persiste que pendant un seul appel à une fonction, ce qui la rend utile pour stockage des informations qui ne sont utilisées que pendant votre fonction et peuvent être supprimées une fois que vous avez terminé) par rapport à l'allocation dynamique (variables dont la durée est définie au moment de l'exécution, au lieu du temps de compilation comme statique ou automatique).

Bien que la plupart des compilateurs et des interprètes implémentent ce comportement de manière similaire en termes d'utilisation de piles, tas, etc., un compilateur peut parfois rompre ces conventions s'il le souhaite tant que le comportement est correct. Par exemple, en raison de l'optimisation, une variable locale ne peut exister que dans un registre ou être supprimée entièrement, même si la plupart des variables locales existent dans la pile. Comme cela a été souligné dans quelques commentaires, vous êtes libre d'implémenter un compilateur qui n'utilise même pas de pile ou de tas, mais à la place d'autres mécanismes de stockage (rarement effectués, car les piles et les tas sont parfaits pour cela).

Je vais fournir un code C annoté simple pour illustrer tout cela. La meilleure façon d'apprendre est d'exécuter un programme sous un débogueur et de surveiller le comportement. Si vous préférez lire python, passez à la fin de la réponse :)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

Un exemple particulièrement poignant de la raison pour laquelle il est important de faire la distinction entre durée de vie et portée est qu'une variable peut avoir une portée locale mais une durée de vie statique - par exemple, "someLocalStaticVariable" dans l'exemple de code ci-dessus. De telles variables peuvent rendre nos habitudes de dénomination communes mais informelles très déroutantes. Par exemple, lorsque nous disons « local », nous voulons généralement dire « variable allouée automatiquement à portée locale » et lorsque nous disons global, nous voulons dire « variable allouée statiquement à portée mondiale ». Malheureusement, quand il s'agit de choses comme " les variables allouées statiquement à portée de fichier ", beaucoup de gens disent juste ... " hein ??? ".

Certains choix de syntaxe en C / C ++ aggravent ce problème - par exemple, beaucoup de gens pensent que les variables globales ne sont pas "statiques" en raison de la syntaxe indiquée ci-dessous.

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

Notez que la mise du mot clé "statique" dans la déclaration ci-dessus empêche var2 d'avoir une portée globale. Néanmoins, le var1 global a une allocation statique. Ce n'est pas intuitif! Pour cette raison, j'essaie de ne jamais utiliser le mot "statique" lors de la description de la portée, et de dire à la place quelque chose comme "fichier" ou "fichier limité". Cependant, de nombreuses personnes utilisent l'expression «statique» ou «étendue statique» pour décrire une variable qui n'est accessible qu'à partir d'un seul fichier de code. Dans le contexte de la durée de vie, "statique" signifie toujours que la variable est allouée au démarrage du programme et désallouée à la fin du programme.

Certaines personnes pensent que ces concepts sont spécifiques à C / C ++. Ils ne sont pas. Par exemple, l'exemple Python ci-dessous illustre les trois types d'allocation (il y a quelques différences subtiles possibles dans les langages interprétés que je n'entrerai pas ici).

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones
davec
la source
Je ferais référence à une variable statique déclarée dans une fonction comme n'ayant qu'une accessibilité locale , mais n'utiliserais généralement pas le terme "portée" avec elle. En outre, il peut être intéressant de noter que le seul aspect pile / tas avec lequel les langues ont une flexibilité essentiellement nulle: un langage qui enregistre le contexte d'exécution sur une pile ne peut pas utiliser cette même pile pour contenir des éléments qui devront survivre aux contextes dans lesquels ils sont créés . Certaines langues comme PostScriptont plusieurs piles, mais ont un "tas" qui se comporte plus comme une pile.
supercat
@supercat Tout cela a du sens. J'ai défini la portée comme "quelles parties du code peuvent accéder à une variable" (et je pense que c'est la définition la plus standard), donc je pense que nous sommes d'accord :)
davec
Je considérerais la «portée» d'une variable comme étant limitée par le temps ainsi que par l'espace. Une variable à portée de classe-objet est requise pour conserver sa valeur tant que l'objet existe. Une variable dans une portée de contexte d'exécution est nécessaire pour conserver sa valeur tant que l'exécution reste dans ce contexte. Une déclaration de variable statique crée un identifiant dont la portée est limitée au bloc actuel, qui est attaché à une variable dont la portée est illimitée.
supercat
@supercat C'est pourquoi j'utilise le mot durée de vie, c'est ainsi que j'appelle ce que vous appelez la durée. Cela réduit le besoin de surcharger le mot «portée» avec autant de significations. Pour autant que je sache, il ne semble pas y avoir de consensus total sur les définitions exactes, même parmi les sources canoniques. Ma terminologie est tirée en partie de K&R et en partie de l'utilisation courante dans le premier département CS où j'ai étudié / enseigné. Toujours bon d'entendre une autre vue éclairée.
davec
1
tu te moques de moi. pouvez-vous vraiment définir une variable statique à l'intérieur d'une fonction?
Zaeem Sattar
168

D'autres ont assez bien répondu aux grands traits, je vais donc apporter quelques détails.

  1. La pile et le tas n'ont pas besoin d'être singuliers. Une situation courante dans laquelle vous avez plus d'une pile est si vous avez plus d'un thread dans un processus. Dans ce cas, chaque thread a sa propre pile. Vous pouvez également avoir plusieurs segments de mémoire, par exemple, certaines configurations de DLL peuvent entraîner l'allocation de différentes DLL à partir de différents segments de mémoire, c'est pourquoi il est généralement mauvais de libérer de la mémoire allouée par une bibliothèque différente.

  2. En C, vous pouvez bénéficier de l'allocation de longueur variable en utilisant alloca , qui alloue sur la pile, par opposition à alloc, qui alloue sur le tas. Cette mémoire ne survivra pas à votre déclaration de retour, mais elle est utile pour un tampon de travail.

  3. Faire un énorme tampon temporaire sur Windows dont vous n'utilisez pas beaucoup n'est pas gratuit. Cela est dû au fait que le compilateur générera une boucle de sonde de pile qui est appelée à chaque fois que votre fonction est entrée pour vous assurer que la pile existe (car Windows utilise une seule page de garde à la fin de votre pile pour détecter le moment où elle doit agrandir la pile. Si vous accédez à la mémoire de plusieurs pages à la fin de la pile, vous vous planterez). Exemple:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}
Don Neufeld
la source
Re "par opposition à alloc": Voulez-vous dire "par opposition à malloc"?
Peter Mortensen
À quel point est-il portable alloca?
Peter Mortensen
@PeterMortensen ce n'est pas POSIX, la portabilité n'est pas garantie.
Don Neufeld
135

D'autres ont répondu directement à votre question, mais en essayant de comprendre la pile et le tas, je pense qu'il est utile de considérer la disposition de la mémoire d'un processus UNIX traditionnel (sans threads et mmap()allocateurs basés sur). La page Web Glossaire de gestion de la mémoire contient un schéma de cette disposition de la mémoire.

La pile et le tas sont traditionnellement situés aux extrémités opposées de l'espace d'adressage virtuel du processus. La pile se développe automatiquement lors de l'accès, jusqu'à une taille définie par le noyau (qui peut être ajustée avec setrlimit(RLIMIT_STACK, ...)). Le segment s'agrandit lorsque l'allocateur de mémoire appelle l' appel système brk()ou sbrk(), mappant plus de pages de mémoire physique dans l'espace d'adressage virtuel du processus.

Dans les systèmes sans mémoire virtuelle, tels que certains systèmes intégrés, la même disposition de base s'applique souvent, sauf que la pile et le tas sont de taille fixe. Cependant, dans d'autres systèmes embarqués (tels que ceux basés sur les microcontrôleurs Microchip PIC), la pile de programmes est un bloc de mémoire distinct qui n'est pas adressable par les instructions de déplacement des données, et ne peut être modifié ou lu qu'indirectement via les instructions de flux de programme (appel, retour, etc.). D'autres architectures, telles que les processeurs Intel Itanium, ont plusieurs piles . En ce sens, la pile est un élément de l'architecture CPU.

bk1e
la source
117

Qu'est-ce qu'une pile?

Une pile est une pile d'objets, généralement celle qui est soigneusement organisée.

Entrez la description de l'image ici

Les piles dans les architectures informatiques sont des régions de mémoire où les données sont ajoutées ou supprimées de la manière du dernier entré, premier sorti.
Dans une application multithread, chaque thread aura sa propre pile.

Qu'est-ce qu'un tas?

Un tas est une collection désordonnée de choses empilées au hasard.

Entrez la description de l'image ici

Dans les architectures informatiques, le tas est une zone de mémoire allouée dynamiquement qui est gérée automatiquement par le système d'exploitation ou la bibliothèque du gestionnaire de mémoire.
La mémoire sur le tas est allouée, désallouée et redimensionnée régulièrement pendant l'exécution du programme, ce qui peut entraîner un problème appelé fragmentation.
La fragmentation se produit lorsque des objets mémoire sont alloués avec de petits espaces entre eux qui sont trop petits pour contenir des objets mémoire supplémentaires.
Le résultat net est un pourcentage de l'espace de tas qui n'est pas utilisable pour d'autres allocations de mémoire.

Les deux ensemble

Dans une application multithread, chaque thread aura sa propre pile. Mais, tous les différents threads partageront le tas.
Étant donné que les différents threads partagent le tas dans une application multithread, cela signifie également qu'il doit y avoir une certaine coordination entre les threads afin qu'ils n'essaient pas d'accéder et de manipuler les mêmes morceaux de mémoire dans le tas à le même temps.

Quel est le plus rapide - la pile ou le tas? Et pourquoi?

La pile est beaucoup plus rapide que le tas.
Cela est dû à la façon dont la mémoire est allouée sur la pile.
L'allocation de mémoire sur la pile est aussi simple que de déplacer le pointeur de pile vers le haut.

Pour les débutants en programmation, c'est probablement une bonne idée d'utiliser la pile car c'est plus facile.
Parce que la pile est petite, vous voudrez l'utiliser lorsque vous savez exactement combien de mémoire vous aurez besoin pour vos données, ou si vous savez que la taille de vos données est très petite.
Il est préférable d'utiliser le tas lorsque vous savez que vous aurez besoin de beaucoup de mémoire pour vos données, ou que vous n'êtes pas sûr de la quantité de mémoire dont vous aurez besoin (comme avec un tableau dynamique).

Modèle de mémoire Java

Entrez la description de l'image ici

La pile est la zone de mémoire où sont stockées les variables locales (y compris les paramètres de méthode). En ce qui concerne les variables d'objet, ce ne sont que des références (pointeurs) aux objets réels sur le tas.
Chaque fois qu'un objet est instancié, un morceau de mémoire de tas est mis de côté pour contenir les données (état) de cet objet. Étant donné que les objets peuvent contenir d'autres objets, certaines de ces données peuvent en fait contenir des références à ces objets imbriqués.

Shreyos Adikari
la source
115

La pile est une partie de la mémoire qui peut être manipulée via plusieurs instructions de langage d'assemblage clés, telles que 'pop' (supprimer et renvoyer une valeur de la pile) et 'push' (pousser une valeur dans la pile), mais aussi appeler ( appeler un sous-programme - cela pousse l'adresse à retourner dans la pile) et retourner (retour d'un sous-programme - cela fait sortir l'adresse de la pile et y saute). C'est la région de mémoire sous le registre de pointeur de pile, qui peut être définie selon les besoins. La pile est également utilisée pour transmettre des arguments aux sous-programmes, ainsi que pour conserver les valeurs dans les registres avant d'appeler des sous-programmes.

Le tas est une partie de la mémoire qui est donnée à une application par le système d'exploitation, généralement via un appel système comme malloc. Sur les systèmes d'exploitation modernes, cette mémoire est un ensemble de pages auxquelles seul le processus appelant a accès.

La taille de la pile est déterminée lors de l'exécution et n'augmente généralement pas après le lancement du programme. Dans un programme C, la pile doit être suffisamment grande pour contenir chaque variable déclarée dans chaque fonction. Le tas augmentera dynamiquement selon les besoins, mais le système d'exploitation fait finalement l'appel (il augmentera souvent le tas de plus que la valeur demandée par malloc, de sorte qu'au moins certains futurs mallocs n'auront pas besoin de retourner au noyau pour obtenir plus de mémoire. Ce comportement est souvent personnalisable)

Parce que vous avez alloué la pile avant de lancer le programme, vous n'avez jamais besoin de malloc avant de pouvoir utiliser la pile, c'est donc un léger avantage. En pratique, il est très difficile de prédire ce qui sera rapide et ce qui sera lent dans les systèmes d'exploitation modernes qui ont des sous-systèmes de mémoire virtuelle, car la façon dont les pages sont implémentées et où elles sont stockées est un détail d'implémentation.

Daniel Papasian
la source
2
Il convient également de mentionner ici que Intel optimise fortement les accès à la pile, en particulier des choses telles que la prévision de l'endroit où vous revenez d'une fonction.
Tom Leys
113

Je pense que beaucoup d'autres personnes vous ont donné des réponses plutôt correctes à ce sujet.

Un détail qui a cependant été omis est que le "tas" devrait en fait probablement être appelé le "magasin gratuit". La raison de cette distinction est que le magasin gratuit d'origine a été implémenté avec une structure de données connue sous le nom de "tas binomial". Pour cette raison, l'allocation depuis les premières implémentations de malloc () / free () était l'allocation à partir d'un tas. Cependant, de nos jours, la plupart des magasins gratuits sont mis en œuvre avec des structures de données très élaborées qui ne sont pas des tas binomiaux.


la source
8
Un autre point de mire - la plupart des réponses (légèrement) impliquent que l'utilisation d'une «pile» est requise par la Clangue. Il s'agit d'une idée fausse courante, bien que ce soit le paradigme (de loin) dominant pour la mise en œuvre C99 6.2.4 automatic storage duration objects(variables). En fait, le mot «pile» n'apparaît même pas dans le C99langage standard: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
johne
[@Heath] J'ai un petit commentaire sur votre réponse. Jetez un œil à la réponse acceptée à cette question . Il dit que le magasin gratuit est probablement le même que le tas , mais pas nécessairement.
OmarOthman
91

Vous pouvez faire des choses intéressantes avec la pile. Par exemple, vous avez des fonctions comme alloca (en supposant que vous pouvez passer les avertissements copieux concernant son utilisation), qui est une forme de malloc qui utilise spécifiquement la pile, pas le tas, pour la mémoire.

Cela dit, les erreurs de mémoire basées sur la pile sont parmi les pires que j'ai connues. Si vous utilisez la mémoire de tas et que vous dépassez les limites de votre bloc alloué, vous avez une chance décente de déclencher une erreur de segment. (Pas à 100%: votre bloc peut être accessoirement contigu à un autre que vous avez précédemment alloué.) Mais comme les variables créées sur la pile sont toujours contiguës, l'écriture hors limites peut modifier la valeur d'une autre variable. J'ai appris que chaque fois que je sens que mon programme a cessé d'obéir aux lois de la logique, c'est probablement un débordement de tampon.

Peter
la source
À quel point est-il portable alloca? Par exemple, cela fonctionne-t-il sous Windows? Est-ce uniquement pour les systèmes d'exploitation de type Unix?
Peter Mortensen
89

Simplement, la pile est l'endroit où les variables locales sont créées. En outre, chaque fois que vous appelez un sous-programme, le compteur de programme (pointeur vers la prochaine instruction machine) et tous les registres importants, et parfois les paramètres sont poussés sur la pile. Ensuite, toutes les variables locales à l'intérieur du sous-programme sont poussées sur la pile (et utilisées à partir de là). Lorsque le sous-programme se termine, tout cela ressort de la pile. Le PC et les données de registre sont récupérés et remis là où ils se trouvaient, afin que votre programme puisse continuer son chemin joyeux.

Le tas est la zone d'allocations de mémoire dynamique de la mémoire à partir desquelles (appels "nouveaux" ou "allocations" explicites). Il s'agit d'une structure de données spéciale qui peut garder une trace des blocs de mémoire de tailles variables et de leur état d'allocation.

Dans les systèmes "classiques", la RAM était disposée de telle sorte que le pointeur de pile commençait au bas de la mémoire, le pointeur de tas commençait au sommet et ils se développaient l'un vers l'autre. S'ils se chevauchent, vous n'avez plus de RAM. Cela ne fonctionne pas avec les systèmes d'exploitation multi-threads modernes. Chaque thread doit avoir sa propre pile, et ceux-ci peuvent être créés dynamiquement.

TED
la source
[@TED] Pourquoi avez-vous dit "parfois les paramètres sont poussés sur la pile"? Ce que je sais, c'est qu'ils le sont toujours . Pourriez-vous nous en dire plus?
OmarOthman
1
@OmarOthman - Je dis cela parce que c'est entièrement à l'auteur de votre compilateur / interprète ce qui se passe quand un sous-programme est appelé. Le comportement classique de Fortran est de ne pas utiliser du tout de pile. Certaines langues prennent en charge des choses exotiques comme le mot de passe, qui est en fait une substitution textuelle.
TED le
83

De WikiAnwser.

Empiler

Lorsqu'une fonction ou une méthode appelle une autre fonction qui à son tour appelle une autre fonction, etc., l'exécution de toutes ces fonctions reste suspendue jusqu'à ce que la toute dernière fonction renvoie sa valeur.

Cette chaîne d'appels de fonction suspendus est la pile, car les éléments de la pile (appels de fonction) dépendent les uns des autres.

La pile est importante à considérer dans la gestion des exceptions et les exécutions de threads.

Tas

Le tas est simplement la mémoire utilisée par les programmes pour stocker les variables. Les éléments du tas (variables) n'ont aucune dépendance les uns avec les autres et peuvent toujours être consultés de manière aléatoire à tout moment.

devXen
la source
"J'aime mieux la réponse acceptée car elle est encore plus faible." C'est une mauvaise chose, pas une bonne chose.
Courses de légèreté en orbite du
54

Empiler

  • Accès très rapide
  • Ne pas avoir à désallouer explicitement les variables
  • L'espace est géré efficacement par le CPU, la mémoire ne sera pas fragmentée
  • Variables locales uniquement
  • Limiter la taille de la pile (en fonction du système d'exploitation)
  • Les variables ne peuvent pas être redimensionnées

Tas

  • Les variables sont accessibles globalement
  • Aucune limite sur la taille de la mémoire
  • Accès (relativement) plus lent
  • Aucune utilisation efficace garantie de l'espace, la mémoire peut se fragmenter au fil du temps lorsque des blocs de mémoire sont alloués, puis libérés
  • Vous devez gérer la mémoire (vous êtes responsable de l'allocation et de la libération des variables)
  • Les variables peuvent être redimensionnées à l'aide de realloc ()
inconnue
la source
50

OK, simplement et en bref, ils signifient ordonné et non ordonné ...!

Stack : Dans les éléments de la pile, les choses se superposent, ce qui signifie que le traitement sera plus rapide et plus efficace! ...

Il y a donc toujours un index pour pointer l'élément spécifique, le traitement sera également plus rapide, il existe également une relation entre les éléments! ...

Tas : Aucun ordre, le traitement sera plus lent et les valeurs sont gâchées sans ordre ni index spécifique ... il y a des aléas et il n'y a pas de relation entre eux ... donc le temps d'exécution et d'utilisation peut varier ...

Je crée également l'image ci-dessous pour montrer à quoi ils peuvent ressembler:

entrez la description de l'image ici

Alireza
la source
49

En bref

Une pile est utilisée pour l'allocation de mémoire statique et un tas pour l'allocation de mémoire dynamique, tous deux stockés dans la RAM de l'ordinateur.


En détail

La pile

La pile est une structure de données "LIFO" (dernier entré, premier sorti), qui est gérée et optimisée par le CPU de manière assez étroite. Chaque fois qu'une fonction déclare une nouvelle variable, elle est "poussée" sur la pile. Ensuite, chaque fois qu'une fonction se termine, toutes les variables poussées sur la pile par cette fonction sont libérées (c'est-à-dire qu'elles sont supprimées). Une fois qu'une variable de pile est libérée, cette région de mémoire devient disponible pour d'autres variables de pile.

L'avantage d'utiliser la pile pour stocker des variables est que la mémoire est gérée pour vous. Vous n'avez pas à allouer de la mémoire à la main, ni à la libérer une fois que vous n'en avez plus besoin. De plus, parce que le CPU organise la mémoire de pile de manière si efficace, la lecture et l'écriture dans les variables de pile sont très rapides.

Plus d'informations peuvent être trouvées ici .


Le tas

Le tas est une région de la mémoire de votre ordinateur qui n'est pas gérée automatiquement pour vous et n'est pas aussi étroitement gérée par le CPU. Il s'agit d'une région de mémoire plus flottante (et plus grande). Pour allouer de la mémoire sur le tas, vous devez utiliser malloc () ou calloc (), qui sont des fonctions C intégrées. Une fois que vous avez alloué de la mémoire sur le tas, vous êtes responsable de l'utilisation de free () pour désallouer cette mémoire une fois que vous n'en avez plus besoin.

Si vous ne le faites pas, votre programme aura ce que l'on appelle une fuite de mémoire. Autrement dit, la mémoire sur le tas sera toujours mise de côté (et ne sera pas disponible pour d'autres processus). Comme nous le verrons dans la section de débogage, il existe un outil appelé Valgrind qui peut vous aider à détecter les fuites de mémoire.

Contrairement à la pile, le tas n'a pas de restrictions de taille sur la taille variable (à part les limitations physiques évidentes de votre ordinateur). La mémoire du tas est légèrement plus lente à lire et à écrire, car il faut utiliser des pointeurs pour accéder à la mémoire sur le tas. Nous parlerons des pointeurs sous peu.

Contrairement à la pile, les variables créées sur le tas sont accessibles par n'importe quelle fonction, n'importe où dans votre programme. Les variables de tas ont une portée essentiellement globale.

Plus d'informations peuvent être trouvées ici .


Les variables allouées sur la pile sont stockées directement dans la mémoire et l'accès à cette mémoire est très rapide, et son allocation est traitée lors de la compilation du programme. Lorsqu'une fonction ou une méthode appelle une autre fonction qui à son tour appelle une autre fonction, etc., l'exécution de toutes ces fonctions reste suspendue jusqu'à ce que la toute dernière fonction renvoie sa valeur. La pile est toujours réservée dans un ordre LIFO, le dernier bloc réservé est toujours le prochain bloc à libérer. Cela rend vraiment simple de garder une trace de la pile, libérer un bloc de la pile n'est rien de plus que d'ajuster un pointeur.

Les variables allouées sur le tas ont leur mémoire allouée au moment de l'exécution et l'accès à cette mémoire est un peu plus lent, mais la taille du tas n'est limitée que par la taille de la mémoire virtuelle. Les éléments du tas n'ont aucune dépendance les uns avec les autres et peuvent toujours être consultés au hasard à tout moment. Vous pouvez allouer un bloc à tout moment et le libérer à tout moment. Cela rend beaucoup plus complexe le suivi des parties du tas allouées ou libres à un moment donné.

Entrez la description de l'image ici

Vous pouvez utiliser la pile si vous savez exactement combien de données vous devez allouer avant la compilation et qu'elle n'est pas trop volumineuse. Vous pouvez utiliser le tas si vous ne savez pas exactement combien de données vous aurez besoin lors de l'exécution ou si vous avez besoin d'allouer beaucoup de données.

Dans une situation multithread, chaque thread aura sa propre pile complètement indépendante, mais ils partageront le tas. La pile est spécifique au thread et le tas est spécifique à l'application. La pile est importante à considérer dans la gestion des exceptions et les exécutions de threads.

Chaque thread obtient une pile, alors qu'il n'y a généralement qu'un seul segment pour l'application (bien qu'il ne soit pas rare d'avoir plusieurs segments pour différents types d'allocation).

Entrez la description de l'image ici

Au moment de l'exécution, si l'application a besoin de plus de segment, elle peut allouer de la mémoire à partir de la mémoire libre et si la pile a besoin de mémoire, elle peut allouer de la mémoire à partir de la mémoire allouée à la mémoire libre pour l'application.

Même, plus de détails sont donnés ici et ici .


Venons-en maintenant aux réponses à votre question .

Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou le langage d'exécution?

Le système d'exploitation alloue la pile pour chaque thread au niveau du système lorsque le thread est créé. En règle générale, le système d'exploitation est appelé par le langage d'exécution pour allouer le segment de mémoire à l'application.

Plus d'informations peuvent être trouvées ici .

Quelle est leur portée?

Déjà donné en haut.

"Vous pouvez utiliser la pile si vous savez exactement combien de données vous devez allouer avant la compilation et qu'elle n'est pas trop grande. Vous pouvez utiliser le tas si vous ne savez pas exactement combien de données vous aurez besoin au moment de l'exécution ou si vous devez allouer beaucoup de données. "

Plus d'informations peuvent être trouvées ici .

Qu'est-ce qui détermine la taille de chacun d'eux?

La taille de la pile est définie par le système d' exploitation lors de la création d'un thread. La taille du segment de mémoire est définie au démarrage de l'application, mais elle peut augmenter à mesure que l'espace est nécessaire (l'allocateur demande plus de mémoire au système d'exploitation).

Qu'est-ce qui rend un plus rapide?

L'allocation de pile est beaucoup plus rapide car elle ne fait que déplacer le pointeur de pile. En utilisant des pools de mémoire, vous pouvez obtenir des performances comparables grâce à l'allocation de tas, mais cela s'accompagne d'une légère complexité supplémentaire et de ses propres maux de tête.

En outre, la pile par rapport au tas n'est pas seulement une considération de performance; il vous en dit également beaucoup sur la durée de vie attendue des objets.

Les détails peuvent être trouvés ici .

Abrar Jahin
la source
36

Dans les années 1980, UNIX s'est propagé comme des lapins avec de grandes entreprises qui roulaient les leurs. Exxon en avait un, tout comme des dizaines de marques perdues dans l'histoire. La façon dont la mémoire a été organisée était à la discrétion des nombreux implémenteurs.

Un programme C typique a été présenté à plat en mémoire avec une possibilité d'augmenter en changeant la valeur brk (). En règle générale, le HEAP était juste en dessous de cette valeur brk et l'augmentation de brk augmentait la quantité de tas disponible.

La pile unique était typiquement une zone en dessous de HEAP qui était une étendue de mémoire ne contenant rien de valeur jusqu'au sommet du bloc de mémoire fixe suivant. Ce bloc suivant était souvent CODE qui pouvait être écrasé par des données de pile dans l'un des célèbres hacks de son époque.

Un bloc de mémoire typique était BSS (un bloc de valeurs nulles) qui n'a pas été accidentellement mis à zéro dans l'offre d'un fabricant. Un autre était DATA contenant des valeurs initialisées, y compris des chaînes et des nombres. Un troisième était CODE contenant CRT (runtime C), main, fonctions et bibliothèques.

L'avènement de la mémoire virtuelle sous UNIX modifie de nombreuses contraintes. Il n'y a aucune raison objective pour laquelle ces blocs doivent être contigus, ou de taille fixe, ou commandés d'une manière particulière maintenant. Bien sûr, avant UNIX, il y avait Multics qui ne souffrait pas de ces contraintes. Voici un schéma montrant l'une des dispositions de mémoire de cette époque.

Disposition de mémoire d'un programme UNIX C typique des années 1980

jlettvin
la source
36

pile , tas et données de chaque processus en mémoire virtuelle:

pile, tas et données statiques

Yousha Aleayoub
la source
26

Quelques cents: Je pense que ce sera bien de dessiner une mémoire graphique et plus simple:

C'est ma vision de la construction de la mémoire de processus avec simplification pour une compréhension plus facile de ce qui se passe


Flèches - montrent où se développent la pile et le tas, la taille de la pile de processus a une limite, définie dans le système d'exploitation, les limites de taille de la pile de threads par les paramètres de l'API de création de threads généralement. Heap limitant généralement la taille maximale de la mémoire virtuelle par processus, pour 32 bits 2-4 Go par exemple.

Façon si simple: le tas de processus est général pour le processus et tous les threads à l'intérieur, utilisant pour l'allocation de mémoire dans le cas commun avec quelque chose comme malloc () .

La pile est une mémoire rapide pour le stockage dans des cas de retour de fonction et des variables de cas courants, traités comme des paramètres dans l'appel de fonction, des variables de fonction locales.

Maxim Akristiniy
la source
23

Étant donné que certaines réponses sont allées de pair, je vais contribuer mon acarien.

Étonnamment, personne n'a mentionné que plusieurs piles d'appels (c'est-à-dire non liées au nombre de threads au niveau du système d'exploitation) se trouvent non seulement dans les langages exotiques (PostScript) ou les plates-formes (Intel Itanium), mais aussi dans les fibres , les threads verts et quelques implémentations de coroutines .

Les fibres, les fils verts et les coroutines sont à bien des égards similaires, ce qui crée beaucoup de confusion. La différence entre les fibres et les fils verts est que les premiers utilisent le multitâche coopératif, tandis que les seconds peuvent être coopératifs ou préemptifs (ou même les deux). Pour la distinction entre fibres et coroutines, voir ici .

Dans tous les cas, le but des fibres, des fils verts et des coroutines est d'avoir plusieurs fonctions s'exécutant simultanément, mais pas en parallèle (voir cette question SO pour la distinction) au sein d'un seul thread de niveau OS, transférant le contrôle d'avant en arrière les uns des autres de façon organisée.

Lorsque vous utilisez des fibres, des fils verts ou des coroutines, vous disposez généralement d'une pile distincte par fonction. (Techniquement, non seulement une pile, mais tout un contexte d'exécution est par fonction. Plus important encore, les registres du processeur.) Pour chaque thread, il y a autant de piles que de fonctions exécutées simultanément, et le thread bascule entre l'exécution de chaque fonction selon la logique de votre programme. Lorsqu'une fonction se termine, sa pile est détruite. Ainsi, le nombre et la durée de vie des piles sont dynamiques et ne sont pas déterminés par le nombre de threads au niveau du système d'exploitation!

Notez que j'ai dit " ont généralement une pile séparée par fonction". Il existe des implémentations à la fois empilées et sans pile de couroutines. La plupart des notables implémentations C ++ stackful sont Boost.Coroutine et Microsoft PPL s » async/await. (Cependant, les fonctions réactivables de C ++ (alias " asyncet await"), qui ont été proposées en C ++ 17, sont susceptibles d'utiliser des coroutines sans pile.)

Une proposition de fibres à la bibliothèque standard C ++ est à venir. En outre, il existe des bibliothèques tierces . Les fils verts sont extrêmement populaires dans des langages comme Python et Ruby.

shakurov
la source
19

J'ai quelque chose à partager, même si les principaux points sont déjà couverts.

Empiler

  • Accès très rapide.
  • Stocké dans la RAM.
  • Les appels de fonction sont chargés ici avec les variables locales et les paramètres de fonction passés.
  • L'espace est libéré automatiquement lorsque le programme sort d'une portée.
  • Stocké dans une mémoire séquentielle.

Tas

  • Accès lent comparativement à Stack.
  • Stocké dans la RAM.
  • Les variables créées dynamiquement sont stockées ici, ce qui nécessite plus tard de libérer la mémoire allouée après utilisation.
  • Stocké partout où l'allocation de mémoire est effectuée, toujours accessible par le pointeur.

Note intéressante:

  • Si les appels de fonction avaient été stockés en tas, cela aurait entraîné 2 points désordonnés:
    1. En raison du stockage séquentiel dans la pile, l'exécution est plus rapide. Le stockage en tas aurait entraîné une énorme consommation de temps, ce qui ralentirait l'exécution de l'ensemble du programme.
    2. Si les fonctions étaient stockées en tas (stockage désordonné pointé par un pointeur), il n'y aurait eu aucun moyen de revenir à l'adresse de l'appelant (quelle pile donne en raison du stockage séquentiel en mémoire).
Pankaj Kumar Thapa
la source
1
concis et propre. nice :)
ingconti
13

Hou la la! Tant de réponses et je ne pense pas que l'une d'elles ait bien compris ...

1) Où et quoi sont-ils (physiquement dans la mémoire d'un vrai ordinateur)?

La pile est une mémoire qui commence comme l'adresse mémoire la plus élevée allouée à l'image de votre programme, puis sa valeur diminue à partir de là. Il est réservé aux paramètres de fonction appelés et à toutes les variables temporaires utilisées dans les fonctions.

Il y a deux tas: public et privé.

Le segment privé commence sur une limite de 16 octets (pour les programmes 64 bits) ou sur une limite de 8 octets (pour les programmes 32 bits) après le dernier octet de code de votre programme, puis augmente en valeur à partir de là. Il est également appelé tas par défaut.

Si le tas privé devient trop grand, il chevauchera la zone de pile, tout comme la pile chevauchera le tas s'il devient trop grand. Étant donné que la pile commence à une adresse supérieure et descend jusqu'à une adresse inférieure, avec un piratage approprié, vous pouvez obtenir une pile si grande qu'elle dépassera la zone de tas privée et chevauchera la zone de code. L'astuce consiste alors à chevaucher suffisamment la zone de code que vous pouvez connecter au code. C'est un peu délicat à faire et vous risquez un plantage du programme, mais c'est facile et très efficace.

Le tas public réside dans son propre espace mémoire en dehors de l'espace image de votre programme. C'est cette mémoire qui sera siphonnée sur le disque dur si les ressources mémoire se raréfient.

2) Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou le langage d'exécution?

La pile est contrôlée par le programmeur, le tas privé est géré par le système d'exploitation et le tas public n'est contrôlé par personne car il s'agit d'un service du système d'exploitation - vous faites des demandes et elles sont accordées ou refusées.

2b) Quelle est leur portée?

Ils sont tous globaux pour le programme, mais leur contenu peut être privé, public ou global.

2c) Qu'est-ce qui détermine la taille de chacun d'eux?

La taille de la pile et du segment privé est déterminée par vos options d'exécution du compilateur. Le tas public est initialisé lors de l'exécution à l'aide d'un paramètre de taille.

2d) Qu'est-ce qui rend un plus rapide?

Ils ne sont pas conçus pour être rapides, ils sont conçus pour être utiles. La façon dont le programmeur les utilise détermine si elles sont "rapides" ou "lentes"

RÉF:

https://norasandler.com/2019/02/18/Write-a-Compiler-10.html

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate

ar18
la source
8

Beaucoup de réponses sont correctes en tant que concepts, mais il faut noter qu'une pile est nécessaire au matériel (ie microprocesseur) pour permettre d'appeler des sous-routines (CALL en langage assembleur ..). (Les gars OOP l'appelleront méthodes )

Sur la pile, vous enregistrez les adresses de retour et appelez → push / ret → pop est géré directement dans le matériel.

Vous pouvez utiliser la pile pour passer des paramètres .. même si c'est plus lent que d'utiliser des registres (dirait un gourou du microprocesseur ou un bon livre du BIOS des années 80 ...)

  • Sans pile, aucun microprocesseur ne peut fonctionner. (on ne peut pas imaginer un programme, même en langage assembleur, sans sous-programmes / fonctions)
  • Sans le tas, c'est possible. (Un programme en langage assembleur peut fonctionner sans, car le tas est un concept de système d'exploitation, comme malloc, c'est-à-dire un appel OS / Lib.

L'utilisation de la pile est plus rapide car:

  • Est matériel, et même push / pop sont très efficaces.
  • malloc nécessite d'entrer en mode noyau, d'utiliser un verrou / sémaphore (ou d'autres primitives de synchronisation) exécutant du code et de gérer certaines structures nécessaires pour garder une trace de l'allocation.
ingconti
la source
Qu'est-ce que l'OPP? Voulez-vous dire la POO (programmation orientée objet )?
Peter Mortensen
Voulez-vous dire que mallocc'est un appel du noyau?
Peter Mortensen
1) oui, désolé .. OOP ... 2) malloc: j'écris brièvement, désolé ... malloc est dans l'espace utilisateur .. mais peut déclencher d'autres appels .... le fait est que l'utilisation du tas PEUT être très lente ...
ingconti
" Beaucoup de réponses sont correctes en tant que concepts, mais il faut noter qu'une pile est nécessaire au matériel (ie microprocesseur) pour permettre d'appeler des sous-routines (CALL en langage assembleur ..) ". Vous confondez la pile du processeur (s'il y en avait un dans le processeur moderne) et les piles d'exécution du langage (une par thread). Lorsque les programmeurs parlent d'une pile, c'est la pile d'exécution de threads du runtime, par exemple une pile de threads NET), nous ne parlons pas de la pile CPU.
minutes
1

La pile est essentiellement une mémoire facile d'accès qui gère simplement ses éléments comme une pile - enfin -. Seuls les articles dont la taille est connue à l'avance peuvent être placés dans la pile . C'est le cas pour les nombres, les chaînes, les booléens.

Le tas est une mémoire pour les éléments dont vous ne pouvez pas prédéterminer la taille et la structure exactes . Étant donné que les objets et les tableaux peuvent être mutés et modifiés au moment de l'exécution, ils doivent aller dans le tas.

Source: Academind

NattyC
la source
0

Merci pour une très bonne discussion mais en tant que vrai noob je me demande où sont conservées les instructions? Au DÉBUT, les scientifiques décidaient entre deux architectures (von NEUMANN où tout est considéré comme DATA et HARVARD où une zone de mémoire était réservée aux instructions et une autre aux données). En fin de compte, nous avons opté pour le design von Neumann et maintenant tout est considéré comme «le même». Cela a été difficile pour moi lorsque j'apprenais l'assemblage https://www.cs.virginia.edu/~evans/cs216/guides/x86.html car ils parlent de registres et de pointeurs de pile.

Tout ce qui précède parle de DATA. Ma conjecture est que, comme une instruction est une chose définie avec une empreinte mémoire spécifique, elle irait sur la pile et donc tous les «ces» registres discutés en assembleur sont sur la pile. Bien sûr, est ensuite venue la programmation orientée objet avec des instructions et des données intégrées dans une structure qui était dynamique, alors maintenant les instructions seraient également conservées sur le tas?

aquagremlin
la source