Comprendre l'allocation de mémoire dans Delphi

Appelez une fois la fonction "DoStackOverflow" à partir de votre code et vous obtiendrez le EStackOverflow erreur relevée par Delphi avec le message "stack overflow".


 une fonction DoStackOverflow: entier;

commencer

 résultat: = 1 + DoStackOverflow;

fin;

Quelle est cette "pile" et pourquoi il y a un débordement en utilisant le code ci-dessus?

Ainsi, la fonction DoStackOverflow s'appelle récursivement - sans «stratégie de sortie» - elle continue de tourner et ne se termine jamais.

Une solution rapide, vous le feriez, consiste à supprimer le bogue évident que vous avez et à vous assurer que la fonction existe à un moment donné (afin que votre code puisse continuer à s'exécuter à partir de l'endroit où vous avez appelé la fonction).

Vous continuez, et vous ne regardez jamais en arrière, sans vous soucier du bug / exception car il est maintenant résolu.

Pourtant, la question demeure: quelle est cette pile et pourquoi y a-t-il un débordement?

Mémoire dans vos applications Delphi

Lorsque vous démarrez la programmation dans Delphi, vous pouvez rencontrer un bogue comme celui ci-dessus, vous le résolvez et continuez. Celui-ci est lié à l'allocation de mémoire. La plupart du temps, vous ne vous souciez pas de l'allocation de mémoire tant que vous libérez ce que vous créez.

Au fur et à mesure que vous acquérez de l'expérience dans Delphi, vous commencez à créer vos propres classes, à les instancier, à vous soucier de la gestion de la mémoire, etc..

Vous arriverez au point où vous lirez, dans l'aide, quelque chose comme "Les variables locales (déclarées dans les procédures et fonctions) résident dans les applications empiler." et aussi Les classes sont des types de référence, elles ne sont donc pas copiées lors de l'affectation, elles sont transmises par référence et elles sont allouées sur le tas.

Alors, qu'est-ce que "pile" et qu'est-ce que "tas"?

Pile vs tas

Exécution de votre application sur Windows, il existe trois zones dans la mémoire où votre application stocke les données: la mémoire globale, le tas et la pile.

Les variables globales (leurs valeurs / données) sont stockées dans la mémoire globale. La mémoire des variables globales est réservée par votre application au démarrage du programme et reste allouée jusqu'à la fin de votre programme. La mémoire des variables globales est appelée "segment de données".

Étant donné que la mémoire globale n'est allouée et libérée qu'une fois à la fin du programme, nous ne nous en soucions pas dans cet article.

La pile et le tas sont l'endroit où l'allocation de mémoire dynamique a lieu: lorsque vous créez une variable pour une fonction, lorsque vous créez une instance d'une classe lorsque vous envoyez des paramètres à une fonction et utilisez / transmettez sa valeur de résultat.

Qu'est-ce que la pile?

Lorsque vous déclarez une variable à l'intérieur d'une fonction, la mémoire requise pour contenir la variable est allouée à partir de la pile. Vous écrivez simplement "var x: integer", utilisez "x" dans votre fonction, et lorsque la fonction se termine, vous ne vous souciez pas de l'allocation de mémoire ni de la libération. Lorsque la variable sort du domaine (le code quitte la fonction), la mémoire qui a été prise sur la pile est libérée.

La mémoire de la pile est allouée dynamiquement en utilisant l'approche LIFO ("dernier entré premier sorti").

Dans les programmes Delphi, la mémoire de pile est utilisée par

  • Variables de routine locales (méthode, procédure, fonction).
  • Paramètres de routine et types de retour.
  • Appels de fonction API Windows.
  • Enregistrements (c'est pourquoi vous n'avez pas à créer explicitement une instance d'un type d'enregistrement).

Vous n'avez pas besoin de libérer explicitement la mémoire sur la pile, car la mémoire est automatiquement allouée pour vous lorsque, par exemple, vous déclarez une variable locale à une fonction. Lorsque la fonction se termine (parfois même avant en raison de l'optimisation du compilateur Delphi), la mémoire de la variable sera automatiquement libérée.

La taille de la mémoire de la pile est, par défaut, suffisamment grande pour vos programmes Delphi (aussi complexes soient-ils). Les valeurs "Taille maximale de la pile" et "Taille minimale de la pile" des options de l'éditeur de liens pour votre projet spécifient des valeurs par défaut - dans 99,99%, vous n'auriez pas besoin de modifier cela..

Considérez une pile comme une pile de blocs de mémoire. Lorsque vous déclarez / utilisez une variable locale, le gestionnaire de mémoire Delphi choisit le bloc par le haut, l'utilise et lorsqu'il n'est plus nécessaire, il sera renvoyé à la pile..

La mémoire des variables locales étant utilisée à partir de la pile, les variables locales ne sont pas initialisées lorsqu'elles sont déclarées. Déclarez une variable "var x: integer" dans une fonction et essayez simplement de lire la valeur lorsque vous entrez dans la fonction - x aura une valeur "bizarre" non nulle. Donc, initialisez toujours (ou définissez la valeur) à vos variables locales avant de lire leur valeur.

En raison de LIFO, les opérations de pile (allocation de mémoire) sont rapides car seules quelques opérations (push, pop) sont nécessaires pour gérer une pile.

Qu'est-ce que le tas?

Un tas est une région de mémoire dans laquelle la mémoire allouée dynamiquement est stockée. Lorsque vous créez une instance d'une classe, la mémoire est allouée à partir du tas.

Dans les programmes Delphi, la mémoire de tas est utilisée par / lorsque

  • Création d'une instance d'une classe.
  • Création et redimensionnement de tableaux dynamiques.
  • Allocation explicite de mémoire à l'aide de GetMem, FreeMem, New et Dispose ().
  • Utilisation de chaînes, variantes, interfaces ANSI / wide / Unicode (géré automatiquement par Delphi).

La mémoire de tas n'a pas de disposition agréable où il y aurait un certain ordre d'allocation de blocs de mémoire. Le tas ressemble à une boîte de billes. L'allocation de mémoire à partir du tas est aléatoire, un bloc d'ici qu'un bloc de là. Ainsi, les opérations de tas sont un peu plus lentes que celles de la pile.

Lorsque vous demandez un nouveau bloc de mémoire (c'est-à-dire créez une instance d'une classe), le gestionnaire de mémoire Delphi s'en charge pour vous: vous obtiendrez un nouveau bloc de mémoire ou un bloc utilisé et rejeté..

Le tas se compose de toute la mémoire virtuelle (RAM et espace disque).

Allocation manuelle de mémoire

Maintenant que tout sur la mémoire est clair, vous pouvez en toute sécurité (dans la plupart des cas) ignorer ce qui précède et continuer simplement à écrire des programmes Delphi comme vous l'avez fait hier.

Bien sûr, vous devez savoir quand et comment allouer / libérer de la mémoire manuellement.

Le «EStackOverflow» (depuis le début de l'article) a été déclenché car à chaque appel à DoStackOverflow un nouveau segment de mémoire a été utilisé à partir de la pile et la pile a des limites. Aussi simple que cela.