Att använda pekare i C-kod (Bör användas av de som känner sig lite hemma med C-programmering!) Rev 1, 2005-11-23 av Ted Wolfram www.wolfram.se
Syfte: Man kan tycka att det är komplicerat att använda pekare när man programmerar i C men det finns tre stora fördelar som man måste beakta: Programmet kommer att gå mycket snabbare samt att man slipper använda globala variabler som tar upp en massa ramminne. Man kommer även att spara ROM-minne också om man gör det på ett snyggt sätt vilket visas i exemplen nedan Vad är en pekare? En pekare är inget annat än en variabel som pekar på en minnesadress. Detta är väldigt användbart då man ska skicka långa strängar eller arrayer till en funktion och sedan göra något med den som t.ex. förändrar dess värde innan man går vidare. Med pekare räcker det att bara skicka startadressen för hela textsträngen! Även om man bara skickar in en textsträng för utskrift på t.ex. en display är det att föredra om man använder pekare. Man slipper allokera en massa RAM-minne och det hela går snabbare. Varför använda dessa På följande sidor har jag bifogat tre exempel på hur effektivt det är med pekare istället för andra lösningar Dessa exempel är kompilerade med HITECH PICC18 v8.35pl2 och avsedda att användas med PIC18 serien. Alla program utför samma sak; Det finns två arrayer, array1 och array2, de är båda 200 byte långa och ges två olika startvärden. Därefter är vitsen att man ska öka alla värden i de båda arrayerna med 1. Givetvis är detta ett enkelt exempel på hur man kan göra, vanligtvis gör ju funktionen något helt annat än att bara addera 1 till var indexvärde
Exempel 1 Här använder vi inte pekare utan man anropar en funktion som ökar värdet med 1, sedan sparas det i en global variabel som man sedan måste kopiera resultatet från för att få tillbaka det förändrade värdet till string1 eller string2. Så här måste man göra om man vill få tillbaka resultatet i den sträng som skickades in i funktionen och använda det till något vettigt. #include <pic18.h> static char glob_array[200]; void main_utanpekare(unsigned char array[]) glob_array[i] = array[i] + 1; // Öka vart index med 1... void main() static unsigned char array1[200], array2[200]; // Ge array1 & array2 startvärden... array1[i] = i; array2[i] = 200 - i; main_utanpekare(array1); // Öka strängens värde med 1 array1[i] = glob_array[i]; // Kopiera till array1 main_utanpekare(array2); array2[i] = glob_array[i]; // Samma sak med array2... Efter att ha kompilerat detta program upptog det: 230 byte ROM 603 byte RAM Tiden det tog att köra programmet från main_utanpekare(array1); till tog 16418 instruktionscykler!
Exempel 2 Här använder vi pekare istället och då ser programmet ut enligt följande: #include <pic18.h> void main_pekare(unsigned char *parray) *parray = *parray + 1; // Öka värdet med 1.. // OBS! Det går inte att skriva // *parray++!!!! parray++; // Öka pekaradressen med 1... void main() static unsigned char array1[200], array2[200]; // Ge array1 & array2 startvärden... array1[i] = i; array2[i] = 200 - i; main_pekare(&array1[0]); // Öka arrayens värde med 1 main_pekare(&array2[0]); // Samma sak med array2... Efter att ha kompilerat detta program upptog det: 138 byte ROM Exempel 1 blev 1,6 ggr större, detta gäller givetvis inte alls för större program! 403 byte RAM Här blev det 200 byte mindre pga. att det inte behövs någon global variabel! Det viktigaste av allt är dock att tiden det tog att köra de två funktionerna main_pekare(&array1[0]); och main_pekare(&array2[0]); endast blev: 4026 instruktionscykler! Det är mer än 4 ggr snabbare!
Exempel 3 Mitt exempel gör ju inget annat än adderar 1 till alla element och i och med det går det givetvis att hoppa över funktionsanropet och göra en forloop som sköter det hela för var array. Givetvis krävs denna loop för var array, vilket man slipper om man har en funktion som sköter detta. Detta funkar ju inte om nu funktionen utför lite mer avancerade saker än att bara addera 1 till arrayen för då blir det ohållbart mycket kod att kompilera. (Samma kod till var array som ska bearbetas!) OK, då visar vi hur det kan gå till. #include <pic18.h> void main() static unsigned char array1[200]; static unsigned char array2[200]; // Ge array1 & array2 startvärden... array1[i] = i; array2[i] = 200 - i; array1[i]++; // Öka array1 med 1... array2[i]++; // Öka array2 med 1... Efter att ha kompilerat detta program upptog det: 130 byte ROM Exempel 2 blev faktiskt 1,1 ggr större, men lägg till en array till som ska modifieras så blir det större än Exempel 2! 400 byte RAM Här blev det bara 400 byte i RAM. Hur lång tid tog det då att köra de två for-looparna? Jo det tog 4800 instruktionscykler vilket innebär att det tog 1,2 ggr längre tid än Exempel 2! Skulle man sedan ha 3 arrayer att kopiera så skulle Exempel 3 ta upp mer ROM än Exempel 3.
Att använda pekare i C Översikt Som det har nämnts tidigare är pekare en variabel som pekar på en adress i processorns RAM-minne. OBS! Det är viktigt att man håller reda på vart i minnet man är och arbetar i, särskilt när man håller på med strängar och arrayer. Annars kan det lätt bli konstiga problem om man pekar utanför den sträng/array man vill arbeta med och därmed ändrar i någon annan variabel. OBS! Deklarationer: Här är ett exempel på hur pekare deklareras: unsigned char intepekare, intepekarr[10]; unsigned char *pekare1, *pekare2, *pekare3; pekare1 = &intepekare; // 1 *pekare1 = 5; // 2 pekare2 = &intepekarr[0]; // 3 pekare3 = &pekare2[1]; // 4 Förklaring: 1. pekare1 pekar nu på intepekare:s RAM-adress 2. Detta är samma sak som att skriva: intepekare = 5; 3. pekare2 pekar på intepekarr[0]:s RAM-adress När man pekar på en array måste man ange ett index på vart i arrayen man pekar. 4. pekare3 pekar på intepekarr[1]:s RAM-adress Likaså här måste man ange ett index i förhållande till pekaren vart man pekar, kan vara negativt! Sammanfattning Genom att sätta en * framför variabelnamnet i deklarationen berättar det för kompilatorn att det är en pekare. Ett & framför en variabel ger adressen till var den är lagrad i RAMminnet. En * framför en pekare behandlar värdet för adressen den pekar på.
Exempel: Om man ska skriva ett mellanslag på alla positioner genom att använda pekaren gör man följande: unsigned char txt[10], *ptxt; ptxt = &txt[0]; for(i=0;i<10;i++) *ptxt = ; ptxt++; // Spara ett mellanslag på den adress // pekaren pekar på... // Peka på nästa fält i strängen När man skriver en * framför pekaren i funktionen betyder det att man sparar i adressen! Så i vårt fall sparas ett mellanslag på den adress som pekaren pekar på. Koden: ptxt++; gör inget annat än räknar upp adressen. Pekare i funktionsanrop Om man vill anropa en funktion som använder pekare gör man följande: // Funktion som använder pekare... void pfunct(unsigned char *p) *p = *p + *p; // Dubbla värdet i den RAM-adress som *p pekar på. void main(); unsigned char foo = 8; pfunct(&foo); // Skicka in RAM-adressen för foo // foo kommer nu att vara 16!! Här finns det en funktion som har pekare i sitt funktionsanrop, då måste den variabel som skickas till funktionen ha ett & framför sig i funktionsanropet. Vad händer här då? Jo variabeln foo är en vanlig char och därmed måste man skicka in RAM-adressen för denna variabel när man anropar funktionen pfunct(); Adressen får man om man skriver & framför den variabel som skickas in. I detta fall &foo.
Väl inne i funktionen pfunct har pekaren p samma adress som foo och därmed måste man för att förändra värdet på p skriva *p. Raden *p = *p + *p; gör följande: *p = värdet för den adress som *p pekar på och därmed adderas det med sig själv och sparas på samma adress. Skicka arrayer till funktioner Om man vill skicka in adressen till en array så får man göra följande: // Funktion som använder pekare... void funcarr(unsigned char *p) for(i=0;i<10;i++) *p = i; // Ge pekarens adress värdet i // (Adressen är inte lika med i!!!) P++; // Öka pekaren med 1 void main(); unsigned char arr[10]; funcarr(&arr[0]); // Skicka in RAM-adressen till arr[0] När man använder strängar måste man skicka en adress i arrayen. I exemplet ovan skickar vi in adressen till position 0 (&foo[0]), givetvis kan man skicka in vilken adress som helst, även adresser utanför arrayens längd vilket kan medföra vissa problem som ni kanske förstår, kompilatorn varnar dock om detta inträffar...
Skicka en pekare till en funktion som anropas med pekare Om man vill skicka vidare en pekare från en funktion till ytterligare en funktion som anropas med pekare gör man följande: // Funktion 1 som använder pekare... void funcarr1(unsigned char *p) funcarr2(p); // Eftersom p är en adress behöver // man inte skriva & framför p!!! // Funktion 2 som använder pekare... void funcarr2(unsigned char *p) *p = *p + 1; // Öka adressens värde med 1 => foo = foo + 1!! void main(); unsigned char foo; Funcarr1(&foo); // Skicka in RAM-adressen till foo Skicka en pekare till en funktion som inte anropas med pekare Om man vill skicka vidare en pekare från en funktion till ytterligare en funktion som inte anropas med pekare gör man följande: // Funktion 1 som använder pekare... void funcptr(unsigned char *p) funcnotptr(*p); // Eftersom p är en adress måste man // skriva * framför p för att få värdet!!! // Funktion 2 som inte använder pekare... void funcnotptr(unsigned char test) test++; // test = foo + 1!! Däremot ändras inte foo!! void main(); unsigned char foo; funcptr(&foo); // Skicka in RAM-adressen till foo Här är det bara värdet på test som ändras eftersom man bara skickade in värdet på *p!
Strängar i funktionsanrop När man hanterar textsträngar som till skillnad från arrayer alltid avslutas med 0x00 som sista tecken behöver man inte skriva ett & framför strängnamnet. Detta bara för att de automatiskt tolkas som en pekare. Peka på text i programminnet (ROM) Om man vill peka på minnet i ROM måste man skriva const framför pekarvariabeln för att visa kompilatorn att texten ligger i ROM-minnet, dock är ju inte pekaren en konstant! Pekarens fysiska storlek Om man har en pekare som pekar på en array som är deklarerad som char kommer givetvis pekaren att vara mer än 8 bitar lång. Detta måste den ju vara eftersom adressen som den pekar mot är ju i stort sett alltid större än 8 bitar eller adressvärdet 256