Hoe Malloc en Free dynamisch geheugen beheren in C

11

Het direct toewijzen van geheugen is de manier waarop C-programma’s groeien en krimpen terwijl ze worden uitgevoerd. U kent de exacte behoeften van uw toepassing pas wanneer deze daadwerkelijk wordt uitgevoerd. De oplossing is de hoop. U vraagt ​​een blokkering aan. Het besturingssysteem reserveert dit. Jij gebruikt het. Dan retourneer je het met ‘gratis’. Met deze cyclus kunnen andere programma’s hetzelfde RAM-geheugen recyclen.

Denk eens aan dit eenvoudige voorbeeld. Het is de meest basale interactie met de hoop die je kunt krijgen.

Hoe Malloc eigenlijk werkt

De malloc -functie is geen magie. Het is een monteur die de inventaris controleert. Als je het roept, gebeuren er drie dingen achter elkaar.

  1. Het controleert de huidige capaciteit van de heap. Er wordt gekeken naar de parameter die je hebt doorgegeven, in dit geval ‘sizeof(int)’, wat 4 bytes is. Er wordt gevraagd of er voldoende aaneengesloten ruimte bestaat. Als de heap gefragmenteerd of vol is, mislukt deze.
  2. Als er ruimte is, reserveert het dat blok. Het systeem markeert het als ‘van jou’. Geen enkele andere ‘malloc’-oproep kan dit deel raken. Het voorkomt onbedoelde botsingen tussen toewijzingen.
  3. Het retourneert het adres van dat blok. Het geeft u niet de gegevens zelf. Het geeft je een pointervariabele. Die variabele bevat de locatie. Je hebt nu toegang tot een specifieke plek in het RAM-geheugen.

In ons voorbeeld ontvangt p dat adres. De pointervariabele is slechts een container voor een geheugenlocatie. De werkelijke gehele waarde bevindt zich op die locatie.

Waarom u moet controleren op NULL

Direct na de toewijzing ziet u de controle if (p == 0). Sommige beginners slaan dit over. Het is een fatale fout.

Controleer altijd de aanwijzer na elke oproep naar Malloc om er zeker van te zijn dat de aanwijzer geldig is.

De heapgrootte fluctueert voortdurend. Het hangt af van welke andere processen actief zijn. Het hangt af van hoeveel geheugen ze hebben gepakt. Er is geen garantie dat een oproep zal slagen. Als malloc mislukt, retourneert het nul. Nul is ‘NULL’. Als u probeert een null-pointer te derefereren, crasht uw programma. Moeilijk.

Je zou ‘if (p == NULL)’ of ‘if (!p)’ kunnen schrijven. Ze bedoelen hetzelfde. Maar je moet het controleren. Elke keer.

Wat gebeurt er als u vergeet geheugen vrij te maken?

Als uw programma eindigt en u niet ‘gratis’ hebt aangeroepen, wordt het besturingssysteem opgeschoond. Het geeft de uitvoerbare ruimte vrij. Het maakt de stapel leeg. Het herwint het mondiale geheugen. Het recycleert ook alle lopende heap-toewijzingen.

Is het dus oké om geheugen te lekken aan het einde van een programma? Technisch gezien wel. Het besturingssysteem zorgt voor het opruimen. Er zijn geen langetermijngevolgen voor de machine.

Maar het is een slechte vorm. En nog belangrijker: geheugenlekken tijdens de uitvoering zijn dodelijk. Als je blijft verdelen zonder vrij te geven, verhonger je je eigen proces. De hoop raakt vol. Nieuwe toewijzingen mislukken. Het programma sterft door verstikking, niet door een crash.

Aanwijzingen versus de waarden waarnaar ze verwijzen

Aanwijzingen kunnen verwarrend zijn. Ze onderscheiden zich van de waarden waarnaar ze verwijzen. Hier ziet u hoe dat in de praktijk uitpakt.

Eerste scenario. U wijst één blok toe. Je wijst er twee variabelen naar.

Uitgang:
10
20

Waarom? p en q zijn verschillende verwijzingen. Maar q = p zorgt ervoor dat ze naar hetzelfde geheugenblok verwijzen. Als u de waarde wijzigt via p, verandert deze voor q. Als je het via q verandert, verandert het in p. Het zijn twee handgrepen op dezelfde deur.

Tweede scenario. U wijst twee afzonderlijke blokken toe.

Uitgang:
20

Hier verwijzen p en q naar verschillende locaties. *q bevat 20. *p = *q kopieert de waarde 20 naar de locatie waar p naar verwijst. De aanwijzer p zelf beweegt niet. De waarde op het adres van p verandert.

Het onderscheid is belangrijk. Men verandert het adres dat in de variabele is opgeslagen. De ander wijzigt de gegevens op dat adres. Het verwarren van deze twee leidt tot logische fouten die moeilijker te debuggen zijn dan syntaxisfouten.

Aanwijzingen, geheugen en de vrije val

De uitvoer is 20. Regel 6 zegt alles.

Je zou kunnen denken dat *p = *q en p = q hetzelfde doen. Dat doen ze niet. Men verplaatst gegevens. De andere zetadressen. Met de compiler kunt u *p = *q toekennen omdat beide verwijzingen waarnaar niet wordt verwezen, gehele getallen zijn. Het is een directe uitwisseling van waarden. Maar p = q? Dat is anders. Het verwijst p naar exact hetzelfde geheugenblok als q. Het adres beweegt. De gegevens blijven staan.

Als typen niet overeenkomen, blokkeert de compiler dit. Een pointer naar een geheel getal kan niet naar een tekenreeks verwijzen. De typen moeten op één lijn liggen.

Vier manieren om te initialiseren

Een nieuwe pointervariabele is een verplichting totdat deze wordt geïnitialiseerd.

int *p; creëert een pointer die nergens specifiek naar verwijst. Het wijst op afval. Het verwijderen van verwijzingen is een fout. U heeft een bekende locatie nodig. Er zijn vier manieren om daar te komen.

  1. malloc : hiermee wordt een blok op de heap toegewezen. p heeft nu een geldig adres. De aanwijzer wordt geïnitialiseerd omdat deze naar een specifiek, gereserveerd deel van het geheugen verwijst.
  2. Opdracht : p = q; als q geldig is, wordt p geldig. Het erft het adres van q. Als q afval is, krijgt p ook afval.
  3. Expliciet adres : u kunt p rechtstreeks naar een bekende variabele verwijzen. p = &i; verwijst p naar geheel getal i. Eenvoudig. Direct.
  4. Nul/NULL : p = 0; of p = NULL;. Hierdoor wordt het adres op nul gezet. Het wijst niet op een blok. Het verwijst naar de nullocatie.

Waarom nul gebruiken? Het is een vlag.

U kunt if (p == 0) controleren om te zien of de pointer geldig is. Het systeem herkent dit. Als u probeert een null-pointer te derefereren, crasht het programma. Moeilijk.

p = 0; *p = 5;

De tweede regel mislukt. p wijst naar niets. Je kunt niet naar niets schrijven. Dit gedrag is later van cruciaal belang voor gekoppelde lijsten. Het is een vangnet.

De hoop bevrijden

Het toewijzen van geheugen is niet voldoende. Je moet het teruggeven.

‘malloc’ grijpt ruimte. ‘gratis’ retourneert het.

free(p) doet twee dingen. Ten eerste wordt de reserve van het blok opgeheven. De heap krijgt dat geheugen terug. Het kan worden hergebruikt. Ten tweede wordt p niet geïnitialiseerd. Het wordt niet op magische wijze veilig. U moet p opnieuw initialiseren voordat u het opnieuw kunt gebruiken.

Het blok is uit uw bereik verdwenen. De wijzer bungelt.

Hier is het standaardpatroon voor het verwerken van een enkel geheel getal op de heap:

Dit is grotendeels een demo. Maar het toont de levenscyclus.

sizeof(int) retourneert de grootte in bytes. Op de meeste machines is dat 4. Je zou malloc(4) kunnen hardcoderen. Niet doen. sizeof maakt code draagbaar. Het leest beter. Het past zich aan als de architectuur verandert.

malloc retourneert een algemene aanwijzer. Compilers waarschuwen vaak als je het niet cast. (int *) converteert die generieke blob naar een verwijzing naar een geheel getal. Het komt overeen met p.

free(p) geeft het blok terug aan de hoop.

Structuren en prioriteit van operators

Structuren gedragen zich op dezelfde manier. U wijst een blok toe voor de hele structuur, niet alleen voor velden.

Kijk naar (*p).i = 10;.

Waarom de haakjes?

Omdat . een hogere prioriteit heeft dan *.

Zonder haakjes betekent *p.i *(p.i). Het probeert toegang te krijgen tot i op de aanwijzer p en vervolgens de verwijzing daarvan te verwijderen. Dat is niet wat je wilt. U wilt eerst de verwijzing naar p verwijderen en vervolgens naar i gaan.

De voorrangsregels zijn streng. * bindt in deze context strakker dan .. Haakjes zorgen ervoor dat de dereferentie plaatsvindt voordat het lid toegang krijgt.

De meeste mensen hebben er een hekel aan om herhaaldelijk (*p).i te typen. Het is uitgebreid.

C heeft een afkorting.

p->ik

Het is precies hetzelfde als (*p).i. Maar het is sneller om te typen. Je ziet overal -> in C-code. Het is de standaardmanier om via een pointer toegang te krijgen tot struct-leden.

De hoop is een eindige hulpbron. Zorgvuldig verdelen. Maak de toewijzing consequent ongedaan. Of zie hoe uw geheugen weglekt.