C++-manual
Förutgående | Kapitel 12. Dynamisk minneshantering | Nästa

# Allokering och frigöring av minne

För att kunna allokera och frigöra minne dynamiskt måste man i C++ ha något "ställe" dit minnet kan placeras då det allokerats. I C++ används pekare för att referera till allokerat minne. Via pekaren kan minnet sedan avefereras och användas.

## Allokering av minne

Allokering av minne sker med operatorn `new`. Allmänt ser en allokering ut på följande sätt:

    datatyp * pekarnamn = new datatyp;

Man bör alltså ha en pekare till den datatypen man ämnar allokera minne för, t.ex. `int`. Operatorn `new` påminner om en funktion, men det är egentligen en operator, så den behöver inte parenteser runt sin parameter. Datatypen som ges till `new` måste vara av samma typ som den pekare som skall ta emot det minne som `new` returnerar. Efter en allokering kan man avreferera den använda pekaren och använda denna som vilken variabel som helst. Några exempel på allokeringar:

    int * IntPekare = new int;
    float * FloatPekare;
    FloatPekare = new float;
    string * Text = new string;

Man behöver alltså inte direkt allokera minne till en pekare, utan man kan först deklarera en pekare och sedan allokera minne i ett senare skede. Man kan även allokera minne flera gånger med hjälp av samma pekare. Flera pekare kan även peka på samma minnesområde, precis som flera pekare kan peka på samma variabels minnesadress. Man bör dock observera att om det finns endast en pekare till ett allokerat minnesblock, och denna pekare sätts att peka på något annat, så har man tappat det allokerade minnet. Det finns därefter ingen möjlighet att hitta det på nytt. Man har läckt minne. Se mera info om minnesläckor i avsnittet Minnesläckor.

Vad händer om vårt program allokerar för mycket minne och det tar slut? Detta är ganska sällsynt, men kan hända om minnesläckor förekommer eller om maskinen har litet minne. I så fall kommer `new` att "kasta" en exception som heter `bad_alloc`. Mera om exceptions i Kapitel 20, där exempel på en minnes-exception visas.

## Allokera vektorer

Förutom att man kan allokera enskilda variabler, såsom en enskild `int`, kan man även allokera en vektor av en och samma datatyp med `new`. Den allmänna formen ser då ut så här:

    datatyp * pekarnamn = new datatyp [antal];

Enda skillnaden från när man allokerar en primitiv är att man specificerar antalet element som vektorn skall innehålla inom `[` `]`. Denna storlek är exakt det antal element i vektorn som kan användas. Exempel på några vektorallokeringar:

    int * Buffer = new int [1024 * 1024];
    float * Koordinat;
    Koordinat = new float [3];
    string * Brev = new string [100];

Man kan allokera mycket stora minnesblock som vektorer. Den enda begränsningen är mängden minne i datorn som används. Det första exemplet allokerar en buffer som är över en miljon element stor, och eftersom en `int` är troligtvis fyra bytes stor (eller större) har man allokerat över fyra miljoner bytes (4 MB). Precis som med normala vektorer bör man inte avreferera element som är utanför det allokerade minnesområdet. Om man t.ex. allokerar 10 element är element 0 det första och element 9 det sista som kan användas, precis som med normala vektorer.

De allokerade vektorerna kan sedan användas precis som normala vektorer, utan speciell avreferering el.dyl., eftersom vektorer i sig redan är pekare. Nedan finns ett exempel som dynamiskt allokerar en vektor och fyller denna med data:

    #include <iostream>

    void in (int * Tal, unsigned int Antal) {
      // iterera och läs in tal
      for ( unsigned int Index = 0; Index < Antal; Index++ ) {
        cout << "Ge in ett tal: ";
        cin >> Tal[Index];
      }
    }

    void ut (int * Tal, unsigned int Antal) {
      // iterera och skriv ut våra tal
      for ( unsigned int Index = 0; Index < Antal; Index++ ) {
        cout << "Talet är: " << Tal[Index] << endl;
      }
    }

    int main () {

      unsigned int Antal;
      int * Vektor;

      // hur många tal önskas?
      cout << "Antalet tal i vektorn: ";
      cin >> Antal;

      // allokera en vektor för att hålla alla tal
      Vektor = new int [ Antal ];

      // läs in data och skriv sedan ut igen
      in ( Vektor, Antal );
      ut ( Vektor, Antal );
    }

Processen att hantera dynamiska vektorer är precis likadan som för statiska vektorer, med enda undantaget sättet på vilket de skapas.

## Allokering av sammansatta datatyper

Som man kunde gissa är det inget speciellt med att allokera sammansatta datatyper. De fungerar precis på samma sätt som övrig allokering. Om vi t.ex. tittar på den `struct Person` som vi skapade i avsnittet Sammansatta datatyper i Kapitel 9. Den såg ut så här:

    struct Person {
      string Namn;
      string Adress;
      int    Telefonnummer;
    };

För att allokera ett element av typen `Person` kan man göra följande:

    Person * P = new Person;
    P->Namn = "Joe Foo";
    P->Adress = "DataCity";
    P->Telefonnummer = 5551234;

Noter att vi här använder oss av `->` för att avreferera en pekare till en `struct` istället för avreferering med `*` och punkt. Mera information om detta i avsnittet Sammansatta datatyper i Kapitel 9. Man kan även sätta alla värden på samma gång:

    Person * P = new Person { "Joe Foo", "DataCity", 5551234 };

Vi skall nu skapa ett litet program som hanterar en stack som innehåller element av datatypen `char`. Vi vill göra stacken totalt dynamisk så att man kan sätta till och ta bort element enligt önskan. Detta exempel visar hur man kan använda pekare tillsammans med `struct`. Varje element i stacken bör innehålla en `char`, samt en pekare till elementet under. Vi definierar detta element på följande sätt:

    struct Element {
      // data för detta element
      char Data;
      // nästa element under detta i stacken
      Element * Previous;
    };

Vi har alltid en pekare `Previous` som pekar på elementet under. Vi får således en enkel länkad lista. Första elementet har `Previous = 0.`Då man lägger till ett element kommer det alltid överst på stacken, och dess `Previous` sätts till att peka på det tidigare översta elementet. En enkel stack behöver funktioner för att lägga till ett element på stacken (`push()`), ta bort första elementer (`pop()`) samt en funktion för att testa och stacken är tom (`isEmpty()`). De tre funktionerna kan vi implementera på följande sätt:

    #include <iostream>

    struct Element {
      // data för detta element
      char Data;
      // nästa element under detta i stacken
      Element * Previous;
    };

    // push:a ett element på stacken
    Element * push (Element * Top) {
      char Data;

      cout << "Elementets värde: ";
      cin >> Data;

      // skapa nytt element
      Element * New = new Element;
      New->Data = Data;
      New->Previous = Top;

      // återvänd och returnera nya topp-elementet
      return New;
    }

    // pop:a ett element från stacken
    Element * pop (Element * Top, char & Data) {
      // är stacken tom?
      if ( Top == 0 ) {
        // stacken tom
        return 0;
      }

      // spara data som är lagret i elementet
      Data = Top->Data;

      // spara pekare till det element som blir nytt topp-element
      Element * Tmp = Top->Previous;

      // frigör minne och återvänd
      delete Top;
      return Tmp;
    }

    // kolla om stacken är tom
    bool isEmpty (Element * Top) {

      if ( Top == 0 ) {
       // stacken tom
       return true;
      }

      // stacken inte tom
      return false;
    }

Koden ovan är relativt enkel att förstå. Både `push()` och `pop()` modifierar stacken, så de returnerar därför den nya stacken. Anroparen måste därför "fånga upp" detta returvärde för att undvika fel. En tom stack har alltid värdet 0, eftersom det då inte finns element allokerade. `pop()` returnerar 0 om stacken blir tom. Funktionerna allokerar minne dynamiskt vid en `push()`och frigör det efter en `pop()` (se avsnittet Frigöring av minne för mera information om frigöring av minne). Vi behöver ännu en funktion `main()` som skall använda vår nya stack. Den kan se ut enligt följande:

    int main () {
      Element * Top = 0;
      char Choice = ' ';
      char Data;

      // iterera tills användaren vill avsluta
      while ( Choice != '3' ) {
        cout << "Välj: " << endl << "1 - push" << endl << "2 - pop" << endl;
        cout << "3 - sluta " << endl << "-> ";

        // läs in ett menyval
        cin >> Choice;

        // vad vill användaren göra
        switch ( Choice ) {
        case '1' : Top = push ( Top ); break;
        case '2' :
          // har vi element i listan?
          if ( ! isEmpty ( Top ) ) {
            // ja, poppa och skriv ut det poppade värdet
            Top = pop ( Top, Data );
            cout << "Poppade: " << Data << endl;
          }
          else {
            cout << "Stacken tom!" << endl;
          }
          break;
        }
      }
    }

Här har vi en `while`-loop som itereras ända tills användaren vill avsluta programmet. En liten meny visas där användaren kan välja att lägga eller ta bort ett element. Då vi tar bort ett element kan vi inte göra det ur en tom stack.

Denna stack är relativt begränsad, eftersom den endast kan hantera data av typen `char`, och koden måste ändras för att kunna användas för andra datatyper. Vi kommer senare att förbättra vårt stack-program och göra det mera flexibelt!

## Frigöring av minne

Den största skillnaden mellan normala variabler (även kallade automatiska, eftersom de skapas och förstörs automatiskt) är att variabler som allokerats dynamiskt måste även frigöras. I detta fall skiljer sig C++ från t.ex. Java, där systemet automatiskt frigör data som inte används längre. Detta system kallas garbage collecting. I C++ är det programmerarens uppgift. Denna uppgift kan vålla en hel del huvudbry för ovana programmerare. Med frigöring av minne menas att ett minnesblock som är allokerat med `new` ges tilbaka till operativsystemet för att användas till något annat ändamål. Om man inte frigör använt minne kommer minnet förr eller senare att ta slut.

I exemplet ovan frigjordes inget minne, är programmet då dåligt? Nej, eftersom operativsystemet automatiskt frigör allt minne som finns allokerat då ett program terminerar. För små program med kort körtid fungerar denna approach. Man kan helt enkelt lämna frigörandet av minne till operativsystemet. Om det däremot är frågan om ett program som kommer att ha en lång körtid (t.ex. en demon) eller som använder mycket stora minnesmängder (t.ex. ett grafikhanteringsprogram) är det viktigt att frigöra minne. Mera info om minnesläckor i avsnittet Minnesläckor.

Minne frigörs med operatorn `delete`. Eftersom det är frågan om en operator fungerar den på samma sätt som `new`. Allmänt frigörs minne på följande sätt:

    delete variabel;
    delete [] vektor;

Man kan alltså frigöra både enkla allokerade variabler och vektorer. Enda skillnaden är att man placerar en tom `[]` efter `delete` då man avser en vektor. Några exempel (vi antar att alla pekare är initialiserade med `new`):

    string * Text = new string;
    delete Text;
    int * Buffer = new int [4096];
    delete [] Buffer;

Notera att en vektor som innehåller endast ett element ändå är en vektor. Operatorn `delete` får endast användas på pekare som har returnerats av `new`, eller så på 0. Om `delete` appliceras på 0 har operationen ingen verkan. Om däremot `delete` appliceras på en pekare med ett slumpmässigt värde uppkommer troligtvis felsituationer. T.ex. följande är fel:

    int main () {
      int * A;
      delete A;
    }

Vi kan inte vara säkra på att `A` har ett definierat värde, eller mera specifikt, att den har värdet 0. Om `A` har värdet 0 så händer ingenting, men om `A` har ett slumpmässigt värde försöker vi troligtvis frigöra minne som inte allokerats. Vi kunde korrigera vårt program från ovan så att det explicit frigör allokerat minne, vilket är bra att bli van med:

    int main () {
      unsigned int Antal;
      int * Vektor;

      // hur många tal önskas?
      cout << "Antalet tal i vektorn: ";
      cin >> Antal;

      // allokera en vektor för att hålla alla tal
      Vektor = new int [ Antal ];

      // läs in data och skriv sedan ut igen
      in ( Vektor, Antal );
      ut ( Vektor, Antal );

      // frigör minne explicit
      delete [] Vektor;
    }

Endast `main()` visades, de övriga funktionerna är oförändrade.

Förutgående | Hem | Nästa
Dynamisk minneshantering | Upp | Vanliga fel