Question Quoi et où sont la pile et le tas?


Les livres de langage de programmation expliquent que les types de valeur sont créés sur le empiler, 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 une explication claire de ceci. Je comprends ce que une pile est. Mais,

  • où et quoi 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 la langue d'exécution?
  • Quelle est leur portée?
  • Qu'est-ce qui détermine la taille de chacun d'entre eux?
  • Qu'est-ce qui fait un plus vite?

7116
2017-09-17 04:18


origine


Réponses:


La pile est la mémoire mise de côté comme espace de travail pour un fil 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 réservé le plus récemment 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 de plus qu'un réglage d'un pointeur.

Le tas est la mémoire mise de côté pour l'allocation dynamique. Contrairement à la pile, il n'y a pas de modèle imposé pour l'allocation et la désallocation des blocs du tas; vous pouvez attribuer 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é; Il existe de nombreux allocateurs de tas personnalisés disponibles pour optimiser les performances du tas pour différents modèles d'utilisation.

Chaque thread obtient une pile, alors qu'il n'y a généralement qu'un segment pour l'application (bien qu'il ne soit pas rare d'avoir plusieurs tas 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 la langue d'exécution?

Le système d'exploitation alloue la pile pour chaque thread au niveau du système lorsque le thread est créé. Généralement, le système d'exploitation est appelé par le langage d'exécution pour allouer le tas à l'application.

Quelle est leur portée?

La pile est attachée à un thread, donc lorsque le thread se termine, la pile est récupérée. Le tas est généralement alloué au démarrage de l'application par le moteur d'exécution et est récupéré lorsque l'application (techniquement le processus) se termine.

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

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

Qu'est-ce qui fait un plus vite?

La pile est plus rapide car le modèle d'accès rend trivial l'allocation et la libération de la mémoire (un pointeur / entier est simplement incrémenté ou décrémenté), alors que le tas a une comptabilité beaucoup plus complexe impliquée dans une allocation ou une désallocation. En outre, chaque octet de la pile a tendance à être réutilisé très fréquemment, ce qui signifie qu'il a tendance à être mappé sur le cache du processeur, ce qui le rend très rapide. Une autre performance attendue pour le tas est que le tas, qui est principalement une ressource globale, doit généralement être multi-threading, c'est-à-dire que chaque allocation et désallocation doit être - typiquement - synchronisée avec "tous" les autres accès tas dans le programme.

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


5228
2017-09-17 04:52



Empiler:

  • Stocké dans la RAM de l'ordinateur, tout 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 sur le 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 trop de la pile est utilisée (la plupart du temps à partir d'une récursion infinie ou trop profonde, de très grosses 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 si elle n'est pas trop grande.
  • Habituellement a une taille maximale déjà déterminée lorsque votre programme commence.

Tas:

  • Stocké dans la RAM de l'ordinateur, tout comme la pile.
  • En C ++, les variables du tas doivent être détruites manuellement et ne doivent jamais tomber hors de portée. Les données sont libérées avec delete, delete[], ou free.
  • Plus lent à allouer par rapport aux variables de la pile.
  • Utilisé à la demande pour allouer un bloc de données à utiliser par le programme.
  • Peut avoir une fragmentation quand 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 new ou malloc respectivement.
  • Peut avoir des échecs d'allocation si un trop grand buffer est demandé pour être alloué.
  • Vous utiliserez le tas si vous ne savez pas exactement de 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;

2092
2017-09-17 04:20



Le point le plus important est que tas et 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 l'un sur l'autre dans l'ordre dans lequel ils ont été placés, et vous ne pouvez enlever que le haut (sans renverser le tout).

    Stack like a stack of papers

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

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

    Heap like a heap of licorice allsorts

    L'allocation de tas nécessite de conserver un enregistrement complet de la mémoire allouée et de celle qui ne l'est pas, ainsi qu'une maintenance supplémentaire pour réduire la fragmentation, trouver des segments de mémoire contigus suffisamment grands pour correspondre à la taille demandée, etc. La mémoire peut être libérée à tout moment en laissant de l'espace libre. Parfois, un allocateur de mémoire effectue des tâches de maintenance, telles que la défragmentation de la mémoire en déplaçant la mémoire allouée, ou la collecte des ordures: identification au moment de l'exécution lorsque la mémoire n'est plus visible et désallouée.

Ces images devraient faire un assez bon travail de description des 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 la langue 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 en cours, comme un pointeur sur la fonction à partir de laquelle il a été appelé, et toutes les variables locales. Comme les fonctions appellent d'autres fonctions et retournent ensuite, la pile croît et se rétracte pour conserver les informations des fonctions plus bas dans la pile des appels. Un programme n'a pas vraiment de contrôle d'exécution dessus; c'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, les fonctions de l'API appelant l'application pour effectuer cette allocation. La gestion de la mémoire allouée de manière dynamique, qui est généralement gérée par le système d'exploitation, nécessite un minimum de temps système.

  • Quelle est leur portée?

    La pile d'appels est un concept si bas qu'il ne se rapporte pas à «l'étendue» dans le sens de la programmation. Si vous désassemblez du code, vous verrez des références de style de pointeur relatif à 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 le souhaiteriez, étant donné le fonctionnement de vos langages de programmation. Dans un tas, c'est aussi 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 un adressage virtuel, que le processeur traduit en adresses physiques et il y a des erreurs de page, etc. Ils gardent une trace des pages qui appartiennent à quelles applications. Cependant, vous n'avez jamais vraiment à vous soucier de cela, 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 / libération échoue pour une raison quelconque).

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

    Encore une fois, cela dépend de la langue, 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 contiguë en mémoire (plus de détails dans le dernier paragraphe). Le compilateur de langue ou l'OS détermine sa taille. Vous ne stockez pas de gros morceaux de données sur la pile, donc il sera assez grand pour ne jamais être utilisé, sauf en cas de récursion sans fin indésirable (d'où "stack overflow") 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 modernes et les systèmes d'exploitation, la façon exacte de travailler est très abstraite, vous n'avez donc pas besoin de vous soucier de la façon dont cela fonctionne, sauf que (dans les langues où cela vous permet) vous ne devez pas utiliser de mémoire Vous n'avez pas encore alloué ou la mémoire que vous avez libérée.

  • Qu'est-ce qui fait un plus vite?

    La pile est plus rapide car toute la mémoire libre est toujours contiguë. Aucune liste n'a besoin d'être conservée de tous les segments de la mémoire libre, juste un seul pointeur vers le sommet actuel de la pile. Les compilateurs stockent habituellement ce pointeur dans un endroit spécial, rapide registre dans ce but. De plus, les opérations suivantes sur une pile sont généralement concentrées dans des zones de mémoire très proches, ce qui, à un niveau très faible, est bon pour l'optimisation par les caches sur le processeur.


1259
2018-03-19 14:38



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

La réponse à votre question est spécifique à l'implémentation et peut varier entre les compilateurs et les architectures de processeurs. 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 mappée à la mémoire physique à la demande).
  • Dans un environnement multithread, chaque thread aura sa propre pile complètement indépendante mais partagera 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 chaînée de blocs utilisés et libres. Nouvelles allocations sur le tas (par new ou malloc) sont satisfaits en créant un bloc approprié à partir de l'un des blocs libres. Cela nécessite la mise à jour de la liste des blocs sur le tas. Ce Méta-information à propos des blocs sur le tas est également stocké sur le tas souvent dans une petite zone juste en face de chaque bloc.
  • Au fur et à mesure que le tas grossit, de nouveaux blocs sont souvent attribués des adresses inférieures vers les adresses supérieures. Ainsi, vous pouvez penser au 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 gros bloc peut échouer car aucun des blocs libres n'est assez grand pour satisfaire la demande d'allocation même si la taille combinée des blocs libres peut être suffisamment grande. C'est appelé fragmentation de tas.
  • Lorsqu'un bloc utilisé qui est adjacent à un bloc libre est libéré, 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.

The heap

La pile

  • La pile fonctionne souvent en tandem avec un registre spécial sur le processeur nommé pointeur de pile. Initialement, le pointeur de la pile pointe vers le haut de la pile (l'adresse la plus élevée de la pile).
  • Le processeur a des instructions spéciales pour pousser valeurs sur la pile et sauter les renvoyer de la pile. Chaque pousser stocke la valeur à l'emplacement actuel du pointeur de la pile et diminue le pointeur de la pile. UNE pop récupère la valeur pointée par le pointeur de la pile, puis augmente le pointeur de la pile (ne pas confondre avec le fait que ajouter une valeur à la pile diminue le pointeur de la pile et enlever une valeur augmente il. Rappelez-vous que la pile se développe vers le bas). Les valeurs stockées et récupérées sont les valeurs des registres de la CPU.
  • Lorsqu'une fonction est appelée, la CPU utilise des instructions spéciales qui poussent le courant pointeur d'instructionc'est-à-dire l'adresse du code s'exécutant sur la pile. La CPU passe alors à la fonction en réglant le pointeur d'instruction à l'adresse de la fonction appelée. Plus tard, lorsque la fonction revient, l'ancien pointeur d'instruction est retiré de la pile et l'exécution reprend au niveau du code juste après l'appel de la fonction.
  • Lorsqu'une fonction est entrée, le pointeur de pile est réduit pour allouer plus d'espace sur la pile pour les 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 la 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 est alors capable de remonter la pile à partir du pointeur de la pile en cours pour localiser ces valeurs.
  • Les appels de fonction d'imbrication fonctionnent comme un charme. Chaque nouvel appel va allouer des paramètres de fonction, l'adresse de retour et l'espace pour les variables locales et ceux-ci enregistrements d'activation peut être empilé pour les appels imbriqués et se déroulera correctement lorsque les fonctions reviendront.
  • Comme la pile est un bloc de mémoire limité, vous pouvez provoquer 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 sorte que l'écriture en dessous du bas (l'adresse la plus basse) de la pile déclenche une interruption ou une exception dans la CPU. Cette condition exceptionnelle peut ensuite être interceptée par le moteur d'exécution et convertie en une sorte d'exception de dépassement de pile.

The stack

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 également pour suivre les 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 malloc et C ++ utilise new, mais beaucoup d'autres langues ont la garbage collection.

Cependant, la pile est une fonctionnalité de plus bas niveau étroitement liée à l'architecture du processeur. Faire croître le tas quand 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, la croissance de la pile est souvent impossible car le débordement de pile n'est découvert que lorsqu'il est trop tard; et l'arrêt du fil d'exécution est la seule option viable.


663
2017-07-31 15:54



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

Picture of variables on the stack

Local Variables cela ne doit durer que tant 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 langues, il est essentiel que nous sachions à la compilation la taille d'une variable si nous voulons la stocker dans la pile.

Les objets (dont la taille varie au fur et à mesure que nous les mettons à jour) vont sur le tas parce que nous ne savons pas au moment de leur création combien de temps ils vont durer. Dans de nombreuses langues, le tas est collecté 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 le C / C ++, les structures et les classes peuvent souvent rester sur la pile quand vous n'avez pas affaire à des pointeurs.

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

La différence entre l'allocation de mémoire stack et 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, pile, types de valeur, types de référence, boxe et unboxing - CodeProject

mais sachez qu'il peut contenir des inexactitudes.


350
2017-11-09 12:28



La pile Lorsque vous appelez une fonction, les arguments de cette fonction plus un autre surcoût sont placés sur la pile. Certaines informations (telles que l'endroit où aller au retour) y sont également stockées. Lorsque vous déclarez une variable dans votre fonction, cette variable est également affectée à la pile.

La désallocation de la pile est assez simple car vous la libérez 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 beaucoup de fonctions qui appellent beaucoup d'autres fonctions (ou créez une solution récursive).

Le tas Le tas est un nom générique pour l'endroit où vous mettez 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 êtes susceptible d'utiliser le nouvel opérateur (ou malloc ou équivalent) pour créer chaque vaisseau spatial. Cette allocation va rester en place pendant un certain temps, il est donc probable que nous allons libérer les choses dans un ordre différent de celui que nous avons créé.

Ainsi, le tas est beaucoup plus complexe, car il finit par y avoir des régions de mémoire qui ne sont pas entrelacées avec des morceaux qui sont - la mémoire est fragmentée. Trouver un souvenir gratuit 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é).

la mise en oeuvre La mise en œuvre de la pile et du segment de mémoire est généralement effectuée à l'exécution / au système d'exploitation. Souvent, les jeux et autres applications critiques en termes de performances créent leurs propres solutions de mémoire qui récupèrent une grande partie de la mémoire du tas et la diffusent ensuite en interne pour éviter de devoir compter sur le système d'exploitation pour la mémoire.

Ceci est seulement pratique 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 dans une opération énorme et pouvez jeter le tout dans une autre opération énorme.

Emplacement physique en mémoire Ceci est moins pertinent que vous pensez à cause d'une technologie appelée Mémoire virtuelle ce qui fait que votre programme pense 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 arborescence d'appels devient plus profonde. Les adresses pour le tas ne sont pas prévisibles (c'est-à-dire spécifiques à l'implémentation) et franchement pas importantes.


190
2017-09-17 04:27



Clarifier, cette réponse a des informations incorrectes (thomas fixé sa réponse après les commentaires, cool :)). D'autres réponses évitent simplement d'expliquer ce que signifie l'allocation statique. Donc, je vais vous expliquer les trois principales formes d'allocation et comment elles se rapportent habituellement au tas, à la pile et au segment de données ci-dessous. Je vais aussi montrer quelques exemples en C / C ++ et en Python pour aider les gens à comprendre.

Les variables "statiques" (AKA statiquement allouées) ne sont pas allouées sur la pile. Ne le supposez pas - beaucoup de gens ne le font que parce que "statique" ressemble beaucoup à "pile". Ils n'existent réellement 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 à portée locale (accessible uniquement par la fonction en cours) portée mondiale (peut être consulté n'importe où) bien que la portée puisse devenir beaucoup plus complexe.

La durée de vie fait référence à l'allocation et à la désallocation d'une variable lors de l'exécution du programme. Habituellement, nous pensons à allocation statique (variable va persister pendant toute la durée du programme, ce qui le rend utile pour stocker la même information à travers plusieurs appels de fonction) contre allocation automatique (la variable ne persiste que pendant un seul appel à une fonction, ce qui la rend utile pour stocker des informations qui ne sont utilisées que pendant votre fonction et qui peuvent être supprimées une fois que vous avez terminé). 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 interprètes implémentent ce comportement de la même façon en termes d'utilisation de piles, de tas, etc., un compilateur peut parfois casser ces conventions s'il le souhaite tant que le comportement est correct. Par exemple, en raison de l'optimisation, une variable locale peut seulement exister dans un registre ou être entièrement supprimée, 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 une pile ou un tas, mais plutôt d'autres mécanismes de stockage (rarement fait, puisque 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 d'observer 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 différence entre la durée de vie et la 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 confuses. Par exemple quand nous disons "local"Nous entendons habituellement"variable localement allouée automatiquement allouée"et quand nous disons global, nous entendons généralement"variable allouée statiquement à l'échelle mondiale"Malheureusement quand il s'agit de choses comme"fichier délimité statiquement attribué des variables"Beaucoup de gens disent juste ..."hein ???".

Certains choix de syntaxe en C / C ++ exacerbent ce problème - par exemple beaucoup de gens pensent que les variables globales ne sont pas "statiques" en raison de la syntaxe montré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 mettre le mot clé "static" dans la déclaration ci-dessus empêche var2 d'avoir une portée globale. Néanmoins, le global var1 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 plutôt quelque chose comme "fichier" ou "fichier limité" portée. Cependant, de nombreuses personnes utilisent l'expression "statique" ou "statique" pour décrire une variable accessible uniquement à partir d'un fichier de code. Dans le contexte de la vie, "statique" toujours signifie que la variable est allouée au démarrage du programme et libérée lorsque le programme se termine.

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'aborderai 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

168
2017-09-17 04:48



D'autres ont assez bien répondu aux grandes lignes, alors je vais vous donner 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 plus d'un tas, par exemple certaines configurations DLL peuvent entraîner l'allocation de différentes DLL à partir de différents tas, ce qui explique pourquoi c'est généralement une mauvaise idée 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 que vous n'utilisez pas beaucoup n'est pas gratuit. C'est parce que le compilateur va générer une boucle de sonde de pile qui est appelée chaque fois que votre fonction est entrée pour s'assurer que la pile existe (parce que Windows utilise une seule page de garde à la fin de votre pile). Si vous accédez à plus d'une page mémoire à la fin de la pile, vous vous écraserez). Exemple:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}

155
2017-09-17 07:16



D'autres ont directement répondu à 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). le Glossaire de la gestion de la mémoire page Web a un diagramme 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 tas augmente lorsque l'allocateur de mémoire invoque le brk() ou sbrk() appel système, mappage de plusieurs 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 mise en page 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 des microcontrôleurs Microchip PIC), la pile de programmes est un bloc de mémoire distinct qui ne peut pas être adressé par des instructions de déplacement de données et ne peut être modifié ou lu qu'indirectement 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 du processeur.


126
2017-09-17 04:57



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

Un détail qui a été manqué, cependant, est que le "tas" devrait en fait s'appeler le "magasin libre". 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 à partir des premières implémentations de malloc () / free () était une allocation à partir d'un tas. Cependant, en ce jour moderne, 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.


107
2017-09-17 04:29



La pile est une partie de la mémoire qui peut être manipulée via plusieurs instructions clés du langage d'assemblage, comme 'pop' (retirer et renvoyer une valeur de la pile) et 'push' (pousser une valeur dans la pile), mais aussi appelez un sous-programme - cela pousse l'adresse à retourner à la pile) et revenez (revenez d'un sous-programme - cela fait sortir l'adresse de la pile et saute dessus). C'est la région de la mémoire en dessous du registre du pointeur de la pile, qui peut être définie selon les besoins. La pile est également utilisée pour transmettre des arguments aux sous-programmes, et également 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 au moment de l'exécution et, en règle générale, ne se développe pas après le lancement du programme. Dans un programme C, la pile doit être suffisamment grande pour contenir toutes les variables déclarées dans chaque fonction. Le tas croîtra de manière dynamique au besoin, mais le système d'exploitation finit par faire l'appel (il augmentera souvent le tas de plus de la valeur demandée par malloc, de sorte qu'au moins certains futurs mallocs n'auront pas besoin de revenir au noyau pour avoir 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, donc c'est un léger avantage. En pratique, il est très difficile de prévoir 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 mise en œuvre et l'emplacement des pages sont des détails d'implémentation.


106
2018-06-11 19:42