web-dev-qa-db-fra.com

Quoi et où sont la pile et le tas?

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

  • où et quels 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 le langage d'exécution?
  • Quelle est leur portée?
  • Qu'est-ce qui détermine la taille de chacun d'eux?
  • Qu'est-ce qui fait que l'on est plus rapide? 
7473
mattshane

La pile est la mémoire réservée 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é lors du prochain appel d'une fonction. La pile est toujours réservée dans un ordre LIFO (dernier entré premier sorti); le dernier bloc réservé est toujours le prochain bloc à 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 l’ajustement d’un pointeur.

Le tas est la mémoire réservée à l'allocation dynamique. Contrairement à la pile, il n'y a pas de modèle imposé à l'allocation et à la désallocation de 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é; Il existe de nombreux allocateurs de segments personnalisés permettant d’optimiser les performances des segments pour différents modèles d’utilisation.

Chaque thread obtient une pile, alors qu'il n'y a généralement qu'un seul tas 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 le langage d'exécution?

Le système d'exploitation alloue la pile pour chaque thread au niveau système lors de la création du thread. Généralement, le système d’exploitation de la langue appelle le système d’exploitation pour allouer le segment de mémoire à l’application.

Quelle est leur portée?

La pile est attachée à un thread. Ainsi, lorsque le thread se ferme, 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 moteur d'exécution et est récupéré à la fermeture de l'application (processus technique).

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

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

Qu'est-ce qui le rend plus rapide?

La pile est plus rapide car le modèle d'accès rend trivial l'allocation et la désallocation de mémoire (un pointeur/un 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. Un autre problème de performance pour le segment de mémoire est que le segment de mémoire, qui est principalement une ressource mondiale, doit généralement être multi-threading safe, c’est-à-dire que chaque allocation et chaque allocation doivent être synchronisées avec "tous" les autres accès de segment de mémoire du programme.

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

5529
Jeff Hill

Pile:

  • Stocké dans l'ordinateur RAM exactement comme le tas.
  • Les variables créées sur la pile sortiront du domaine 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 la transmission de paramètres.
  • Peut avoir un débordement de pile lorsque trop de pile est utilisée (principalement par récursion infinie ou trop profonde, allocations très importantes).
  • Les données créées sur la pile peuvent être utilisées sans pointeur.
  • Vous utiliserez la pile si vous savez exactement combien de données vous devez allouer avant la compilation et si elles ne sont pas trop volumineuses.
  • La taille maximale est généralement déjà définie au démarrage de votre programme.

Tas:

  • Stocké dans l'ordinateur RAM exactement comme la pile.
  • En C++, les variables du segment de mémoire doivent être détruites manuellement et ne doivent jamais tomber en dehors de la 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 segment de mémoire sont pointées par des pointeurs et attribuées avec new ou malloc respectivement.
  • Peut avoir des échecs d’allocation si une trop grande quantité de mémoire tampon est demandé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 devez 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;
2199
Brian R. Bondy

Le point le plus important est que tas et pile sont des termes génériques pour des manières d'allouer de la mémoire. Ils peuvent être mis en œuvre de nombreuses manières différentes, et les termes s'appliquent aux concepts de base.

  • Dans une pile d'éléments, les éléments sont placés les uns sur les autres dans l'ordre dans lequel ils ont été placés, et vous ne pouvez supprimer que l'élément supérieur (sans que tout ne soit renversé).

    Stack like a stack of papers

    La simplicité d'une pile est qu'il n'est pas nécessaire de conserver 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 pointeur unique 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 commencer au sommet d'une partie de la 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 sur 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’existe pas d’élément clair en haut.

    Heap like a heap of licorice allsorts

    L'allocation de segment de mémoire nécessite de conserver un enregistrement complet de la mémoire allouée et de celle qui ne l'est pas, ainsi que de la maintenance des frais généraux afin de réduire la fragmentation, de 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 exécute 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 récupération de place, identifiant au moment de l’exécution le moment où la mémoire n’est plus dans le périmètre et la désaffectant. 

Ces images devraient décrire assez bien les deux manières d’allouer et de libérer de la mémoire dans une pile et dans 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 nombreuses manières. Les programmes informatiques ont généralement une pile appelée call stack qui stocke des informations relatives à la fonction en cours, telles qu'un pointeur sur la fonction à partir de laquelle elle a été appelée et les variables locales. Etant donné que les fonctions appellent d'autres fonctions puis reviennent, la pile s'agrandit et se rétrécit pour conserver les informations des fonctions situées plus bas dans la pile d'appels. Un programme n'a pas vraiment le contrôle de son exécution; 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 façon dynamique et aléatoire. c'est-à-dire en panne. La mémoire est généralement allouée par le système d'exploitation, les fonctions d'API appelant l'application effectuant cette allocation. La gestion de la mémoire allouée dynamiquement, qui est généralement gérée par le système d'exploitation, nécessite un temps système considérable.

  • Quelle est leur portée?

    La pile d'appels est un concept tellement bas qu'elle n'a aucun rapport avec la "portée" au sens de programmation. Si vous désassemblez du code, vous verrez des références de style de pointeur relatif à des parties de la pile, mais dans la mesure où un langage de niveau supérieur est concerné, le langage impose ses propres règles de portée. Cependant, un aspect important d’une pile est qu’une fois qu’une fonction est retournée, tout élément local de cette fonction est immédiatement libéré de la pile. Cela fonctionne comme vous le souhaitez, étant donné le fonctionnement de vos langages de programmation. En un tas, c'est aussi difficile à définir. La portée est celle qui est exposée par le système d'exploitation, mais votre langage de programmation ajoute probablement ses règles concernant la "portée" de votre application. L'architecture du processeur et le système d'exploitation utilisent un adressage virtuel, que le processeur traduit en adresses physiques et en cas de défauts de page, etc. Ils gardent une trace des pages appartenant à quelles applications. Cela dit, vous n'avez jamais vraiment besoin de vous inquiéter, 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érifiez les erreurs (si l'attribution/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, il doit s'agir d'une mémoire contiguë (plus de détails dans le dernier paragraphe). Le compilateur de langage ou le système d'exploitation détermine sa taille. Vous ne stockez pas d'énormes quantités de données sur la pile. Elles seront donc suffisamment volumineuses pour ne jamais être pleinement utilisées, sauf en cas de récursion sans fin non souhaitée (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 manière dont vous le regardez, la taille change constamment. De toute façon, dans les processeurs et les systèmes d’exploitation modernes, son fonctionnement exact est très abstrait. Vous n’avez donc normalement pas à vous soucier de son fonctionnement en profondeur, sauf que (dans les langues où cela vous permet) vous n'avez pas encore alloué ou la mémoire que vous avez libérée.Qu'est-ce qui fait que l'on est plus rapide?.

  • What makes one faster?

    The stack is faster because all free memory is always contiguous. No list needs to be maintained of all the segments of free memory, just a single pointer to the current top of the stack. Compilers usually store this pointer in a special, fast register for this purpose. What's more, subsequent operations on a stack are usually concentrated within very nearby areas of memory, which at a very low level is good for optimization by the processor on-die caches.

1305
thomasrutter

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

La réponse à votre question dépend de la mise en œuvre et peut varier d’un compilateur à l’autre et d’une architecture de processeur à l’autre. Cependant, voici une explication simplifiée.

  • La pile et le segment de mémoire sont des zones de mémoire allouées à partir du système d'exploitation sous-jacent (souvent, la mémoire virtuelle mappée sur la mémoire physique à la demande).
  • Dans un environnement multi-thread, 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 libres et utilisés. Les nouvelles allocations sur le tas (par new ou malloc) sont satisfaites 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. Ces méta-informations relatives aux blocs du tas sont également stockées dans le tas, souvent dans une petite zone située juste en face de chaque bloc.
  • Au fur et à mesure que le tas croît, de nouveaux blocs sont souvent alloués depuis les adresses les plus basses vers les adresses les plus hautes. 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 segment de mémoire est trop petit pour une allocation, il est souvent possible d’augmenter la taille en acquérant plus de mémoire à partir du système d’exploitation sous-jacent.
  • L'affectation et la désallocation de nombreux petits blocs peuvent laisser le tas dans un état dans lequel de nombreux petits blocs libres sont dispersés entre les blocs utilisés. Une demande d'allocation d'un bloc volumineux 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 suffisante. Ceci s'appelle fragmentation de tas.
  • Lorsqu'un lot 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 segment de mémoire.

The heap

La pile

  • La pile fonctionne souvent en tandem rapproché avec un registre spécial sur la CPU appelé le 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).
  • La CPU dispose d'instructions spéciales pour les valeurs {push) sur la pile et les sauts de la pile. Chaque Push enregistre la valeur à l'emplacement actuel du pointeur de pile et diminue le pointeur. Un pop récupère la valeur pointée par le pointeur de la pile, puis augmente le pointeur de la pile (ne vous laissez pas troubler par le fait que ajoutant une valeur dans la pile diminue le pointeur de la pile et suppression une valeur augmente elle. Rappelez-vous que la pile se développe vers le bas). Les valeurs stockées et extraites sont les valeurs des registres de la CPU.
  • Lorsqu’une fonction est appelée, la CPU utilise des instructions spéciales qui poussent le pointeur d’instruction} actuel, c’est-à-dire l’adresse du code s’exécutant sur la pile. La CPU passe alors à la fonction en positionnant 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 de la fonction.
  • Lorsqu'une fonction est entrée, le pointeur de la pile est diminué pour allouer plus d'espace sur la pile aux variables locales (automatiques). Si la fonction a une variable locale 32 bits, quatre octets sont mis de côté sur la pile. Au retour de la fonction, le pointeur de la pile est reculé pour libérer la zone attribuée.
  • Si une fonction a des paramètres, ceux-ci sont placés dans la pile avant l'appel de la fonction. Le code de la fonction est ensuite capable de naviguer dans la pile à partir du pointeur de la pile actuel pour localiser ces valeurs.
  • Les appels de fonction de nidification fonctionnent comme un charme. Chaque nouvel appel allouera des paramètres de fonction, l'adresse de retour et de l'espace pour les variables locales. Ces enregistrements d'activation _ peuvent être empilés pour les appels imbriqués et se dérouleront de la bonne manière lors du retour des fonctions.
  • 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 manière à ce que l’écriture en dessous du bas (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ébordement de pile.

The stack

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

Non, les enregistrements d’activation des fonctions (variables locales ou automatiques) sont alloués à la pile 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 de nombreux autres langages ont un garbage collection.Cependant, la pile est une fonctionnalité de bas niveau étroitement liée à l'architecture du processeur. Faire croître le tas lorsqu'il n'y a pas assez d'espace n'est pas 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épassement de capacité de la pile n'est découvert que lorsqu'il est trop tard; et arrêter le fil d'exécution est la seule option viable.

However, the stack is a more low-level feature closely tied to the processor architecture. Growing the heap when there is not enough space isn't too hard since it can be implemented in the library call that handles the heap. However, growing the stack is often impossible as the stack overflow only is discovered when it is too late; and shutting down the thread of execution is the only viable option.

685
Martin Liversage

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 qui ne doit durer que tant que l'invocation de la fonction va dans la pile. Le tas est utilisé pour les variables dont on ne connaît pas vraiment la durée de vie, mais on s'attend à ce qu'elles durent un certain temps. Dans la plupart des langues, il est essentiel que nous sachions au moment de la compilation quelle est la taille d'une variable si nous voulons la stocker sur la pile. 

Les objets (dont la taille varie au fur et à mesure de leur mise à 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 mis au rebut pour rechercher des objets (tels que l'objet cls1) qui ne possèdent plus aucune référence. 

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

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

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

mais sachez qu'il peut contenir des inexactitudes. 

379
Snowcrash

The Stack Lorsque vous appelez une fonction, les arguments de cette fonction ainsi que d'autres frais généraux sont placés dans la pile. Certaines informations (telles que l'emplacement où aller sur le retour) sont également stockées ici . Lorsque vous déclarez une variable dans 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 de celui auquel vous allouez. Les éléments de pile sont ajoutés lorsque vous entrez des 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 appelant de nombreuses autres fonctions (ou créez une solution récursive).

The Heap Le tas est un nom générique indiquant où vous placez les données que vous créez à la volée. Si vous ne savez pas combien de vaisseaux spatiaux vont être créés par votre programme, vous utiliserez probablement le nouvel opérateur (ou malloc ou équivalent) pour créer chaque vaisseau spatial. Cette allocation va rester pendant un moment, il est donc probable que nous libérerons 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 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 devrait être évité (même s'il est encore souvent utilisé).

Implémentation L'implémentation à la fois de la pile et du tas se fait généralement jusqu'à l'exécution/le système d'exploitation. Les jeux et autres applications dont les performances sont critiques créent souvent leurs propres solutions de mémoire qui extraient une grande partie de la mémoire du tas, puis la distribuent en interne pour éviter de s’appuyer sur le système d’exploitation. 

Ceci n’est pratique que si votre utilisation de la mémoire est assez différente de la norme - c’est-à-dire pour les jeux dans lesquels vous chargez un niveau dans une énorme opération et que vous pouvez tout perdre dans une autre énorme opération.

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 donne à votre programme l'impression que vous avez accès à une certaine adresse où les données physiques se trouvent ailleurs ( même sur le disque dur!). Les adresses que vous obtenez pour la pile sont en ordre croissant au fur et à mesure que votre arborescence des appels s’approfondit. Les adresses du tas ne sont pas prévisibles (c'est-à-dire spécifiques à l'implémentation) et ne sont franchement pas importantes.

194
Tom Leys

Pour clarifier, cette réponse a des informations incorrectes ( thomas a corrigé sa réponse après les commentaires, cool :)). Les autres réponses évitent simplement d’expliquer ce que l’allocation statique signifie. J'expliquerai donc les trois principales formes d'allocation et leur relation avec le segment de mémoire, la pile et le segment de données ci-dessous. Je vais aussi montrer quelques exemples en C/C++ et Python pour aider les gens à comprendre.

Les variables "statiques" (AKA statiquement allouées) ne sont pas allouées sur la pile. Ne présumez pas cela - beaucoup de gens le font uniquement parce que "statique" sonne beaucoup comme "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 " la portée " et " la durée de vie "plutôt que" pile "et" tas ".

La portée fait référence aux parties du code pouvant accéder à une variable. En général, on pense à 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 au moment où une variable est allouée et désallouée pendant l'exécution du programme. On pense généralement à une allocation statique (la variable persistera pendant toute la durée du programme, ce qui le rend utile pour stocker la même information sur plusieurs appels de fonction) affectation automatique (la variable ne persiste que lors d'un seul appel à une fonction, ce qui est utile pour stocker des informations utilisées uniquement pendant votre fonction et pouvant être supprimées une fois vous avez terminé) par rapport à allocation dynamique (variables dont la durée est définie au moment de l'exécution, au lieu du temps de compilation, statique ou automatique).

Bien que la plupart des compilateurs et interprètes implémentent ce comportement de la même manière en utilisant des piles, des tas, etc., un compilateur peut parfois enfreindre ces conventions s'il le souhaite tant que le comportement est correct. Par exemple, en raison de l'optimisation, une variable locale peut uniquement exister dans un registre ou être entièrement supprimée, même si la plupart des variables locales existent dans la pile. Comme il 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, car les piles et les tas sont parfaits pour cela).

Je vais fournir un code C simple annoté pour illustrer tout cela. La meilleure façon d'apprendre consiste à exécuter un programme sous un débogueur et à 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 émouvant de la raison pour laquelle il est important de distinguer durée de vie et étendue est le fait 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 nommage communes mais informelles très confuses. Par exemple, lorsque nous disons " local ", nous entendons généralement " localement affecté variable automatiquement allouée " et lorsque nous disons global nous entendons généralement " variable allouée statiquement et globalement étendue ". Malheureusement, quand il s’agit de choses comme " fichier affecté des variables statiquement allouées " beaucoup de gens disent juste que ... " hein ??? ".

Certains choix de syntaxe en C/C++ aggravent ce problème - par exemple, de nombreuses personnes pensent que les variables globales ne sont pas "statiques" à cause de la syntaxe présenté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 l'insertion du mot clé "statique" dans la déclaration ci-dessus empêche var2 d'avoir une portée globale. Néanmoins, la variable globale 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, mais plutôt de dire quelque chose comme "fichier" ou "fichier limité". Cependant, de nombreuses personnes utilisent les expressions "statique" ou "portée statique" pour décrire une variable accessible uniquement à partir d'un fichier de code. Dans le contexte de la durée de vie, "statique" signifie toujours que la variable est allouée au début du programme et désallouée à la sortie du programme.

Certaines personnes considèrent ces concepts comme spécifiques à C/C++. Ils ne sont pas. Par exemple, l'exemple Python ci-dessous illustre les trois types d'allocation (il existe quelques différences subtiles possibles dans les langages interprétés dans lesquelles je ne rentrerai 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 __== "__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
181
davec

D'autres ont très bien répondu aux grandes lignes, alors je vais donner quelques détails.

  1. La pile et le tas ne doivent pas ê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 disposer de plusieurs segments. Par exemple, certaines configurations DLL peuvent entraîner l'allocation de différentes DLL à partir de différents segments, raison pour laquelle il est généralement déconseillé de libérer de la mémoire allouée par une autre bibliothèque.

  2. En C, vous pouvez tirer parti 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 return, 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. Cela est dû au fait que le compilateur générera une boucle de sonde de pile appelée chaque fois que votre fonction est entrée pour vérifier 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 être agrandie. Si vous accédez à la mémoire à plus d’une page de la fin de la pile, vous tomberez en panne). Exemple:

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

D'autres ont directement répondu à votre question, mais lorsque vous essayez de comprendre la pile et le tas, je pense qu'il est utile de prendre en compte la structure de la mémoire d'un processus UNIX traditionnel (sans threads ni allocateurs basés sur mmap()). Le Glossaire de gestion de la mémoire Web contient un diagramme de cette structure de mémoire.

La pile et le tas sont généralement situés aux extrémités opposées de l'espace d'adressage virtuel du processus. La pile croît 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 de mémoire augmente lorsque l'allocateur de mémoire appelle l'appel système brk() ou sbrk(), mappant davantage 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 est souvent appliquée, sauf que la pile et la pile 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 n’est pas adressable par les instructions de mouvement de données et ne peut être modifiée ou lue indirectement que par des instructions de 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 de la CPU.

127
bk1e

La pile est une partie de la mémoire qui peut être manipulée via plusieurs instructions clés du langage d'assemblage, telles que «pop» (supprimer et renvoyer une valeur de la pile) et «Push» (envoyer une valeur dans la pile), mais aussi appeler ( appelez un sous-programme - ceci poussera l'adresse pour retourner à la pile) et retournera (à partir d'un sous-routine - cela extraira l'adresse de la pile et sautera dessus). C'est la région de la mémoire située sous le registre du pointeur de pile, qui peut être définie selon les besoins. La pile est également utilisée pour passer des arguments à des sous-routines, ainsi que pour conserver les valeurs dans les registres avant d'appeler des sous-routines.

Le tas est une partie de la mémoire donnée par le système d'exploitation à une application, 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 ne grandit 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 croîtra dynamiquement selon les besoins, mais le système d'exploitation passe 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, ce qui constitue 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 dotés de sous-systèmes de mémoire virtuelle, car la manière dont les pages sont mises en œuvre et où elles sont stockées est un détail d'implémentation. 

108
Daniel Papasian

Je pense que beaucoup d'autres personnes vous ont donné des réponses généralement correctes à ce sujet.

Un détail qui a été omis, cependant, 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 appelée "tas binomial". Pour cette raison, allouer depuis les premières implémentations de malloc ()/free () était alloué depuis un tas. Cependant, de nos jours, la plupart des magasins gratuits sont implémentés avec des structures de données très élaborées qui ne sont pas des tas binomiaux.

107
Heath

Qu'est-ce qu'une pile?

Une pile est une pile d'objets, généralement un objet bien rangé.

 Enter image description here

Les piles dans les architectures informatiques sont des régions de la mémoire où des données sont ajoutées ou supprimées selon la méthode du dernier entré, premier sorti. 
Dans une application multi-thread, 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.

 Enter image description here

Dans les architectures informatiques, le segment de mémoire 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 segment de mémoire est allouée, désallouée et redimensionnée régulièrement au cours de 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 des espaces réduits trop petits pour contenir des objets mémoire supplémentaires. 
Le résultat net est un pourcentage de l'espace de tas qui ne peut pas être utilisé pour d'autres allocations de mémoire.

Les deux ensemble

Dans une application multi-thread, 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 pour qu'ils n'essayent pas d'accéder et de manipuler le même ou 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. 
Allouer de la mémoire sur la pile est aussi simple que de déplacer le pointeur de la pile vers le haut.

Pour les débutants en programmation, il est probablement judicieux d’utiliser la pile, car c’est plus facile. 
Comme la pile est petite, vous voudrez l’utiliser lorsque vous saurez 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

 Enter image description here

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

101
Shreyos Adikari

Vous pouvez faire des choses intéressantes avec la pile. Par exemple, vous avez des fonctions telles que alloca (en supposant que vous puissiez dépasser les nombreux avertissements 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 liées à la pile sont parmi les pires que j'ai connues. Si vous utilisez de la mémoire de tas et que vous dépassez les limites du bloc que vous avez alloué, vous avez une chance décente de provoquer une défaillance de segment. (Non 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 en dehors des limites peut changer 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, il s'agit probablement d'un débordement de mémoire tampon.

87
Peter

Simplement, la pile est l'endroit où les variables locales sont créées. De plus, chaque fois que vous appelez un sous-programme, le compteur de programme (pointeur sur l'instruction machine suivante) et tous les registres importants, et parfois les paramètres sont incrustés dans la pile. Ensuite, toutes les variables locales à l'intérieur du sous-programme sont placées dans la pile (et utilisées à partir de là). Lorsque le sous-programme est terminé, tous ces éléments sont retirés de la pile. Le PC et la base de données d’enregistrement récupèrent et remettent à l’état où ils se trouvaient au fur et à mesure de leur apparition, pour que votre programme puisse continuer son chemin joyeux.

Le segment de mémoire est la zone de mémoire dynamique. Les allocations de mémoire dynamiques sont effectuées à partir de (nouveaux appels explicites ou "d'allocations"). C'est une structure de données spéciale qui peut garder trace de blocs de mémoire de tailles variables et de leur statut d'allocation.

Dans les systèmes "classiques" RAM, le pointeur de pile commençait en bas de la mémoire, le pointeur de segment de mémoire en haut, 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 multithreads modernes. Chaque thread doit avoir sa propre pile, et ceux-ci peuvent être créés de manière dynamique.

84
T.E.D.

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 constitue la pile, car les éléments de la pile (appels de fonction) dépendent les uns des autres.

La pile est importante à prendre en compte 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.

78
devXen

Pile

  • Accès très rapide
  • Ne devez pas explicitement désaffecter des variables
  • L'espace est géré efficacement par la CPU, la mémoire ne sera pas fragmentée
  • Variables locales uniquement
  • Limite de la taille de la pile (dépend 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 dans le 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 en utilisant realloc ()
50
unknown

OK, simplement et en quelques mots, ils signifient ordonné et pas ordonné ...!

Stack: Dans les éléments de la pile, les choses se superposent, cela signifie qu'il sera plus rapide et plus efficace à traiter! ... 

Donc, il y a 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! ...

Heap: Aucun ordre, le traitement ne sera plus lent et les valeurs sont mélangées sans ordre ni index spécifique ... il y a des variables aléatoires et aucune relation entre elles ... donc l'exécution et le temps d'utilisation peuvent varier. ..

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

 enter image description here

38
Alireza

En bref

Une pile est utilisée pour l'allocation de mémoire statique et un segment de mémoire 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), gérée et optimisée de manière assez étroite par la CPU. 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 placées dans 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 les 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 besoin d'allouer de la mémoire manuellement ni de la libérer une fois que vous n'en avez plus besoin. De plus, la CPU organisant la mémoire de pile de manière efficace, la lecture et l'écriture de variables de pile sont très rapides.

Plus peut être trouvé 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 qui n'est pas gérée aussi étroitement par le processeur. C'est une région plus flottante de la mémoire (et est 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 libérer cette mémoire lorsque vous n'en avez plus besoin.

Si vous ne le faites pas, votre programme aura ce qu’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 segment de mémoire n’est soumis à aucune restriction en matière de taille (en dehors des limitations physiques évidentes de votre ordinateur). La mémoire de tas est un peu plus lente à lire et à écrire, car il faut utiliser des pointeurs pour accéder à la mémoire sur le tas. Nous parlerons de pointeurs sous peu.

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

Plus peut être trouvé ici.


Les variables allouées sur la pile sont stockées directement dans la mémoire. L'accès à cette mémoire est très rapide et son affectation 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 très simple le suivi de la pile, libérer un bloc de la pile n’est rien d’autre que l’ajustement d’un pointeur.

La mémoire est allouée aux variables affectées au segment de mémoire au moment de l'exécution et l'accès à cette mémoire est un peu plus lent, mais la taille de segment de mémoire 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 de manière aléatoire à 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é.

 Enter image description here

Vous pouvez utiliser la pile si vous savez exactement combien de données vous devez allouer avant la compilation, et ce n’est pas trop volumineux. 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.

Dans une situation à plusieurs threads, chaque thread aura sa propre pile complètement indépendante, mais ils partageront le tas. La pile est spécifique au thread et le segment est spécifique à l'application. La pile est importante à prendre en compte 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 tas pour l'application (bien qu'il ne soit pas rare d'avoir plusieurs tas pour différents types d'allocation).

 Enter image description here

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

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


Maintenant, venez aux réponses de 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 système lors de la création du thread. Généralement, le système d’exploitation de la langue appelle le système d’exploitation pour allouer le segment de mémoire à l’application.

Plus peut être trouvé 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 si elle n’est pas trop grosse. 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 peut être trouvé dans ici .Qu'est-ce qui détermine la taille de chacun d'eux?.

Qu'est-ce qui le rend plus rapide?.

De plus, pile contre 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 à partir de ici.

Also, stack vs. heap is not only a performance consideration; it also tells you a lot about the expected lifetime of objects.

Details can be found from here.

35
Abrar Jahin

Dans les années 1980, UNIX se propageait comme un lapin avec de grandes entreprises qui lancent la leur… .. Exxon en possédait une, de même que des dizaines de marques perdues au profit de l’histoire… .. La manière dont la mémoire était aménagée était à la discrétion de nombreux développeurs.

Un programme typique de C était mis à plat en mémoire avec une possibilité d'augmenter en modifiant la valeur de brk () . En règle générale, HEAP était juste au-dessous de cette valeur de brk Et augmenter de plus en augmentait la quantité disponible. tas.

Le STACK unique était généralement une zone située sous HEAP, une étendue de mémoire Ne contenant rien d’important jusqu’au sommet du prochain bloc fixe de mémoire . Ce bloc suivant était souvent CODE et pouvait être écrasé par les données de la piledans l'un des célèbres hacks de son époque.

Un bloc de mémoire typique était BSS (un bloc de valeurs nulles) Accidentellement non 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), principales, fonctions et bibliothèques.

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

A typical 1980s style UNIX C program memory layout

35
jlettvin

stack, heap et data de chaque processus en mémoire virtuelle:

 stack, heap and static data

29
Yousha Aleayoub

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

 This is my vision of process memory construction with simplification for more easy understanding wht happening


Flèches - indiquent où la pile de culture et la pile de croissance, la taille de la pile de processus ont une limite, définie dans le système d’exploitation, la taille de la pile de threads est limitée par les paramètres de l’API de création de thread généralement. Le tas limite généralement par la taille maximale de la mémoire virtuelle du processus, par exemple pour les bits 32 à 4 Go.

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

La pile est une mémoire rapide pour stocker dans des cas communs des pointeurs et des variables de retour de fonction, traités comme des paramètres dans un appel de fonction, des variables de fonction locales.

24
Maxim Akristiniy

Depuis que certaines réponses ont piqué, je vais contribuer à mon acarien.

De manière surprenante, personne n’a mentionné que de multiples piles d’appels (c’est-à-dire non liées au nombre de threads exécutés au niveau du système d’exploitation) se trouvent non seulement dans des langages exotiques (PostScript) ou des plates-formes (Intel Itanium), mais également dans fibres , fils verts et quelques implémentations de coroutines .

Les fibres, les fils verts et les coroutines sont similaires à bien des égards, ce qui entraîne beaucoup de confusion. La différence entre les fibres et les fils verts réside dans le fait que les premiers utilisent le multitâche coopératif, tandis que les derniers peuvent en comporter un en mode coopératif ou préventif (ou même les deux). Pour la distinction entre les fibres et les coroutines, voir ici .

Dans tous les cas, les fibres, les threads verts et les coroutines ont pour but de faire en sorte que plusieurs fonctions s’exécutent simultanément mais pas en parallèle (voir cette SO question pour la distinction) dans un champ. thread unique au niveau du système d'exploitation, transférant le contrôle les uns des autres de manière organisée.

Lorsque vous utilisez des fibres, des fils verts ou des lignes de routines, vous généralement disposez d'une pile distincte par fonction. (Techniquement, pas une pile, mais tout un contexte d'exécution est défini par fonction. Plus important encore, les registres de CPU.) Pour chaque thread, il y a autant de piles que de fonctions s'exécutant simultanément, et le thread bascule entre l'exécution de chaque fonction. selon la logique de votre programme. Quand une fonction va jusqu'au bout, 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 "généralement avoir une pile séparée par fonction". Il y a à la fois stackful et stackless implémentations de cours. Les implémentations C++ superposées les plus remarquables sont Boost.Coroutine et Microsoft PPL 's async/await. (Cependant, les fonctions pouvant être reprises de C++ (a.k.a. "async et await"), qui ont été proposées à C++ 17, sont susceptibles d'utiliser des coroutines sans pile.)

La 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.

21
shakurov

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

Pile  

  • 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 transmis.
  • L'espace est libéré automatiquement lorsque le programme sort d'une portée.
  • Stocké dans la 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 ultérieurement de libérer la mémoire allouée après utilisation.
  • Stocké où que soit alloué la mémoire, toujours accessible par le pointeur.

Note intéressante:

  • Si les appels de fonction avaient été stockés dans le tas, cela aurait créé 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 aurait ralenti l'exécution de l'ensemble du programme.
    2. Si les fonctions étaient stockées dans le tas (stockage en désordre désigné par le pointeur), il n'y aurait aucun moyen de revenir à l'adresse de l'appelant (quelle pile donne en raison du stockage séquentiel en mémoire).
12
Pankaj Kumar Thapa

Beaucoup de réponses sont correctes en tant que concepts, mais nous devons noter qu'une pile est nécessaire au matériel (par exemple, un microprocesseur) pour permettre l'appel de sous-routines (CALL en langage d'assemblage ..). (Les gars OOP l'appelleront method)

Sur la pile, sauvegardez les adresses de retour et appelez → Push/Ret → → Pop est géré directement dans le matériel.

Vous pouvez utiliser la pile pour transmettre des paramètres .. même si elle est plus lente que l'utilisation de registres (dirait un gourou du microprocesseur ou un bon livre de BIOS des années 1980 ...)

  • Sans pile non le microprocesseur peut fonctionner. (nous ne pouvons pas imaginer un programme, même en langage d'assemblage, sans sous-programmes/fonctions)
  • Sans le tas, c'est possible. (Un programme en langage Assembly peut fonctionner sans, car le tas est un concept d’OS, comme malloc, c’est un appel OS/Lib.

L'utilisation de la pile est plus rapide que:

  • Est le 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 au suivi de l'allocation.
8
ingconti

Hou la la! Tant de réponses et je ne pense pas que l'un d'entre eux a bien compris ...

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

La pile est une mémoire qui commence par l'adresse mémoire la plus élevée affectée à votre image de 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 de mémoire privé commence à une limite de 16 octets (pour les programmes 64 bits) ou à 8 octets (pour les programmes 32 bits) après le dernier octet de code de votre programme, puis augmente à partir de là. On l'appelle aussi le tas par défaut.

Si le segment de mémoire privé devient trop volumineux, il chevauchera la zone de pile, de même que la pile se superposera au segment de mémoire s'il devient trop volumineux. Étant donné que la pile commence à une adresse plus élevée et descend jusqu'à une adresse plus basse, avec un piratage approprié, vous pouvez obtenir une pile si grande qu’elle envahira la zone de mémoire privée et recouvrira la zone de code. L'astuce consiste alors à chevaucher suffisamment de la zone de code que vous pouvez intégrer au code. C'est un peu délicat à faire et vous risquez un crash du programme, mais c'est facile et très efficace.

Le segment de mémoire 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 en 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 segment de mémoire privé est géré par le système d’exploitation et le segment de mémoire public n’est contrôlé par personne car il s’agit d’un service de système d’exploitation.

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 le segment de mémoire privé sont déterminés par les options d'exécution du compilateur. Le segment public est initialisé à l'exécution à l'aide d'un paramètre de taille.

2d) Qu'est-ce qui le rend 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 s'ils sont "rapides" ou "lents"

REF:

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

3
ar18

stratégies d'allocation de stockage par langages de programmation 

MÉMOIRE EN C - LA PILE, LE CŒUR ET LA STATIQUE

 enter image description here  MEMORY IN C – THE STACK, THE HEAP, AND STATIC

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

Je veux ajouter quelques points à propos de cette question importante: 

OS et Common Language Runtime 

Composants clés de .NET Framework  enter image description here Architecture Net Framework 4.5  enter image description here

Composants de CLR  Components of CLR  enter image description here  enter image description here  enter image description here

0
leonidaa