Programmeringsspråket C

Pekare

Vad är pekare?

Pekare. Ordet är bekant? Du har hört det förut? Det har kanske nämnts flera gånger redan i den här tutorialen? Vad är det? Pekare är svåra att greppa till en början, och kräver att man har viss koll på hur minneslagring fungerar. Jag har hittills inte kommit över något material som ens försöker förklara pekare utan att missbruka bilder för att visa hur saker fungerar, men vi ska göra vårt bästa.

Variabler har vi redan sagt lagras i minnet som en följd av bitar. Minsta beståndsdelen i minnet är som sagt en bit. Däremot, minsta beståndsdelen man kan be datorn att hämta ett värde ur, är en byte. Du kan med andra ord inte ge datorn en instruktion att "ge mig värdet på den och den biten". Däremot går det utmärkt att instruera datorn att "ge mig värdet på den och den byten". Det har vi redan gjort hur mycket som helst, i och med våran användning av variabler. Nyckelpoängen här är att varje byte har en så kallad adress, minnesadress.

I C kan man jobba antingen direkt med värdena som finns lagrade i vissaplatser, eller, om man så vill, via minnesadresserna. Vi har redan nosat på detta, även om du inte vet det själv. Redan i del 3 utför vi vad vi då, och nu, kallar för en referencing. Ampersand före variabelnamnet betyder att vi hämtar variabelns minnesadress istället för värde. Detta demonstreras i följande program.

#include <stdio.h>
 
int main() {
    int vanligvariabel;
 
    printf("%#x\n", (unsigned int) &vanligvariabel);
 
    return 0;
}

Det ska egentligen inte vara något jättekonstigt med denna kod. Formatet vi använder för att skriva ut minnesadressen kanske ser lite ovanligt ut. %#x betyder att vi vill skriva ut en unsigned int hexadecimalt (%x), och vi vill använda alternativ form (# emellan). Det är på detta sätt man vanligtvis refererar till minnesadresser. Notera även att vi gör en cast till unsigned int, eftersom det är så minnesadresser ser ut. Ampersand innan variabelnamnet betyder att vi utför en referencing, att vi vill komma åt adressen till variabeln, inte själva innehållet i variabeln.

Vad har då detta med pekare att göra? Jo, pekare är liknande variabler, fast istället för att innehålla ett värde innehåller de minnesadressen till ett värde. Titta på följande korta exempel.

int vanligvariabel, *enpekare;
 
vanligvariabel = 42;
enpekare = &vanligvariabel;

Vi deklarerar alltså en vanlig variabel (som lite senare får ett värde). Inget konstigt. Men vi deklarerar också en pekare! Denna pekare får också ett värde, och vad för värde det är kan du redan utläsa. Just det, adressen till våran variabel. Så nu har vi en pekare som pekar på en minnesadress. På denna minnesadress finns värdet 42. Detta går lätt att kontrollera enligt följande exempel (lägg till denna kod till föregående kod).

printf("%i\n", enpekare);

Nej vänta! Nu blev något fel här. Den skrev inte ut 42. Den skrev ut adressen som pekaren pekar på. Varför det? Jo, värdet på en pekare är minnesadressen den pekar på. Precis som i tilldelningen lite tidigare, precis som i printf(). Hur gör man då för att komma åt värdet som pekaren pekar på? Man utför en så kallad dereferencing. Referencing var när man kom åt en variabels minnesadress, dereferencing är alltså motsatsen; när man kommer åt en minnesadress värde. Dereferencing utför man med en asterisk (*) istället för ampersand. Denna rad ska ge väntat resultat.

printf("%i\n", *enpekare);

Nu kom du alltså åt värdet som minnesadressen pekar på istället. Och, bara för att ytterligare klargöra hur det hänger ihop, testa detta: printf("%i\n", *(&vanligvariabel)); Först referencing på vanligvariabel, så man får dess minnesadress. Och sen dereferencing på den adressen. Vilket innebär att man i slutändan kommer åt värdet på variabeln ändå.

Kan man ändra på variablers värde genom pekare? Ja, det kan man, och det är en jätterelevant fråga, eftersom det är det som är hela poängen med pekare. Dock måste man observera viss försiktighet när detta genomförs, eftersom prioritetsreglerna kan förstöra lite för dig. Fortsätt bygga på förra exemplet med följande kod.

*enpekare = 24;    //ändrar värdet dit enpekare pekar till 24
printf("%i\n", vanligvariabel);    //skriver ut värdet av vanligvariabel (kom ihåg att det är till den variabeln som enpekare pekar)
 
*enpekare += 17;
printf("%i\n", vanligvariabel);
 
*enpekare++;    //ap-ap-ap-ap, det här kommer inte gå som man tänker sig
printf("%i\n", vanligvariabel);    //den har inte ändrat värde!
 
*enpekare = 1;    //troligtvis segfault

Var gick allt så fel? *enpekare++ ser ju rätt ut? Nja, prioriteten spökar. Ökningsoperatorn (++) har högre prioritet än dereferencing (*). Det innebär att först ökar pekarens värde (den pekar alltså nu mot en helt ny adress), sen försöker vi komma åt värdet dit den pekar nu. Vi gör inget med värdet, så inget kommer hända (möjligtvis att din kompilator klagar på "statement with no effect"). Sedan skriver vi ut värdet av envariabel, som inte har ändrats ett dugg. Sedan försöker vi ändra värdet dit pekaren pekar nu, och det har vi med största sannolikhet inte alls rätt att göra. Därför får vi en segmentation fault.

Som du kanske redan har gissat ligger lösningen på det här problemet i parenteser. Om man kör (*enpekare)++ så fungerar det utmärkt. Det gäller alltså att vara försiktig med prioriteten när man börjar jobba med referencing och deferencing. Experimentera vidare lite med pekare innan du går vidare till nästa underrubrik, annars kan det bli rörigt.

Pekare med funktioner

Så här kommer den första riktiga nyttan av pekare. Dess användning tillsammans med funktioner. Som du minns från tidigare så tar funktioner ett antal argument, och kan sen returnera ett värde. Och vad händer om man vill returnera flera värden samtidigt? Tänk dig en funktion för att byta plats på två tal.

int swap(int a, int b) {
    int temp;
    temp = a;    //lägga undan värdet i a tillfälligt
    a = b;       //ställa in a till att ha b's värde
    b = temp;    //ställa in b till att ha det värdet från a vi lade undan
 
    //men vad sjutton ska vi returnera? returnerar vi a får vi inte med b, returnerar vi b får vi inte med a
    //detta är olösligt på vanliga sättet
 
    return -1;
}

Som vi ser ställs vi inför ett problem när vi vill returnera mer än ett värde, manipulera mer än en variabel. Lösningen på detta problem är pekare. Hurdå? Kolla exemplet nedan.

void swap(int *a, int *b) {
    int temp;     //vanlig variabel
    temp = *a;    //lägg märke till att det vi hämtar är det värdet som a _pekar på_
    *a = *b;      //ställa in platsen som a pekar till att ha värdet som b pekar på
    *b = temp;    //ställa in platsen som b pekar till att ha det värdet som vi lade undan
 
    //detta är en void-funktion, och behöver inget returnera. vi är klara :)
}

Denna funktion anropar på följande vis med variabler.

int ett, tva;
ett = 4;
tva = 7;
 
swap(&ett, &tva);
//ett har nu tva's värde, och tva har etts

Vi avbryter här för att förklara. I anropet skickar vi alltså två minnesadresser, inte värdet på variablerna (vi utför en referencing). Som synes i funktionen tar den två pekare som argument. Det var precis vad vi skickade (en minnesadress formulerad som C vill är en pekare). Pekare a innehåller alltså minnesadressen till variabeln ett. Pekare b innehåller minnesadressen till variabeln tva.

Det kraftiga med att vi har minnesadresserna är att vi ju kan ändra värdet som de pekar på. När vi går tillbaks till main() kommer variablerna ett och tva fortfarande vara lagrade på samma ställe i minnet som tidigare. Och på det stället hade vi ändrat värdena lite. Resten av swap()-funktionen torde vara ganska tydlig, vi ändrar på värdena som våra pekare pekar på. Vi behöver inte returnera något eftersom vi har ändrat direkt i minnespositionerna som behövdes.

Åt det här hållet gick allt bra. Går det att returnera en pekare från en funktion? Ta en titt på exemplet nedan för att förstå hur jag menar.

#include <stdio.h>
 
int *skaparenvariabel() {
    int variabel;
    variabel = 3;
 
    return &variabel;
}
 
int main() {
    int *pekare;
    pekare = skaparenvariabel();
 
    *pekare = 1;
 
    return 0;
}

Vad händer här? Först skapar vi en pekare till en int i main(). Sen får den ett värde, en adress. Den får den adressen som returneras av skaparenvariabel(). Den funktionen är av typen int *, alltså en pekare till en int. Så visst kan funktioner returnera pekare. På precis det där sättet. Problemet i detta fall är att när den där funktionen har körts klart, så kommer variabel att tas bort (om du har glömt hänvisar jag till första varningsrutan i del 9). Dess scope upphör. Därför har vi inte längre rätt att ändra i den minnespositionen som pekaren nu pekar på. Poängen med detta: håll koll på scopen när du använder pekare tillsammans med funktioner.

I nästa del kommer vi fortsätta gå igenom pekare ännu mer på djupet. Bland annat hur pekare och arrayer hör ihop, samt hur man allokerar valfri mängd minne i C. Det kan bli ännu mer rörigt än denna del, så innan du går vidare till nästa del: Lär. Dig. Pekare. Utan och innan.

← Typer

Pekare, forts. →

Copyleft kqr & slaeshjag 2009, 2012 some rights reserved