Förstå minnesallokering i Delphi

Ring funktionen "DoStackOverflow" en gång från din kod så får du EStackOverflow fel tagit upp av Delphi med meddelandet "stack overflow".


 fungera DoStackOverflow: heltal;

Börja

 resultat: = 1 + DoStackOverflow;

slutet;

Vad är denna "stack" och varför finns det ett översvämning där med koden ovan?

Så, DoStackOverflow-funktionen kallar sig rekursivt - utan en "exit-strategi" - den fortsätter bara att snurra och går aldrig ut.

En snabb lösning, du skulle göra, är att rensa det uppenbara felet du har, och se till att funktionen existerar vid någon tidpunkt (så din kod kan fortsätta köra där du har ringt funktionen).

Du går vidare och tittar aldrig tillbaka och bryr dig inte om felet / undantaget eftersom det nu är löst.

Ändå kvarstår frågan: vad är denna stack och varför finns det ett överflöde?

Minne i dina Delphi-applikationer

När du börjar programmera i Delphi, kan du uppleva fel som ovanstående, du skulle lösa det och gå vidare. Den här är relaterad till minnesallokering. Oftast bryr du dig inte om minnesallokering så länge du frigör det du skapar.

När du får mer erfarenhet av Delphi, börjar du skapa dina egna klasser, instansera dem, bry dig om minneshantering och lika.

Du kommer till den punkt där du kommer att läsa, i Hjälpen, något liknande "Lokala variabler (deklareras inom procedurer och funktioner) finns i en applikations stack." och även Klasserna är referenstyper, så de kopieras inte på uppdraget, de skickas genom referens och de tilldelas på högen.

Så vad är "stack" och vad är "heap"?

Stack mot Heap

När du kör din applikation på Windows finns det tre områden i minnet där din applikation lagrar data: globalt minne, hög och stack.

Globala variabler (deras värden / data) lagras i det globala minnet. Minnet för globala variabler reserveras av din applikation när programmet startar och förblir tilldelat tills ditt program avslutas. Minne för globala variabler kallas "datasegment".

Eftersom det globala minnet bara en gång tilldelas och frigörs vid programavslut, bryr vi oss inte om det i den här artikeln.

Stack och heap är där dynamisk minnesallokering sker: när du skapar en variabel för en funktion, när du skapar en instans av en klass när du skickar parametrar till en funktion och använder / skickar dess resultatvärde.

Vad är stack?

När du förklarar en variabel i en funktion tilldelas minnet som krävs för att hålla variabeln från bunten. Du skriver helt enkelt "var x: heltal", använder "x" i din funktion, och när funktionen går ut bryr du dig inte om minnesallokering eller frigöring. När variabeln går utanför räckvidden (kod lämnar funktionen) frigörs minnet som togs på bunten.

Stackminnet tilldelas dynamiskt med hjälp av LIFO-metoden ("sist i första ut").

I Delphi-program används stackminne av

  • Lokala rutiner (metod, procedur, funktion) variabler.
  • Rutinmässiga parametrar och returtyper.
  • Windows API-funktion samtal.
  • Poster (det är därför du inte behöver uttryckligen skapa en instans av en posttyp).

Du behöver inte uttryckligen frigöra minnet på bunten, eftersom minnet tilldelas automatiskt för dig när du till exempel förklarar en lokal variabel till en funktion. När funktionen avslutas (ibland till och med innan på grund av Delphi-kompilatoroptimering) frigörs minnet för variabeln automatiskt magiskt.

Stackminnestorleken är som standard stor nog för dina (lika komplexa som de är) Delphi-program. Värdena "Maximal stapelstorlek" och "Minsta stackstorlek" i Linker-alternativen för ditt projekt anger standardvärden - i 99,99% skulle du inte behöva ändra detta.

Tänk på en stack som en hög med minnesblock. När du förklarar / använder en lokal variabel kommer Delphi-minneshanteraren att välja blocket uppifrån, använda det och när det inte längre behövs kommer det tillbaka till stacken.

Med lokalt variabelt minne som används från bunten initialiseras inte lokala variabler när de deklareras. Förklara en variabel "var x: heltal" i någon funktion och försök bara läsa värdet när du går in i funktionen - x kommer att ha ett "konstigt" värde som inte är noll. Så initialisera alltid (eller ställa in värde) till dina lokala variabler innan du läser deras värde.

På grund av LIFO går stacken (minnesallokering) snabbt eftersom bara några få operationer (push, pop) krävs för att hantera en stack.

Vad är hög?

En hög är ett minneområde där dynamiskt allokerat minne lagras. När du skapar en instans av en klass tilldelas minnet från högen.

I Delphi-program används heapminne av / när

  • Skapa en instans av en klass.
  • Skapa och ändra storlek på dynamiska matriser.
  • Allmänt fördela minne med GetMem, FreeMem, New och Dispose ().
  • Använda ANSI / wide / Unicode strängar, varianter, gränssnitt (hanteras automatiskt av Delphi).

Heapminne har ingen trevlig layout där det skulle finnas någon ordning är att tilldela block av minne. Heap ser ut som en burk med kulor. Tilldelning av minnet från högen är slumpmässigt, ett block härifrån än ett block därifrån. Således är högoperationerna lite långsammare än de på stacken.

När du ber om ett nytt minnesblock (dvs. skapa en instans av en klass) kommer Delphi-minnehanteraren att hantera detta åt dig: du får ett nytt minnesblock eller ett använt och kasserat ett.

Högen består av allt virtuellt minne (RAM och diskutrymme).

Manuellt tilldela minne

Nu när allt om minnet är klart kan du säkert (i de flesta fall) ignorera ovanstående och helt enkelt fortsätta skriva Delphi-program som du gjorde igår.

Naturligtvis bör du vara medveten om när och hur man man allokerar / fritt minne.

"EStackOverflow" (från början av artikeln) höjdes eftersom för varje samtal till DoStackOverflow har ett nytt minnessegment använts från stacken och stacken har begränsningar. Så enkelt som det.