| E | L | P | H | I | R | O    programmeren •  linux •  gitaar •  foto's •  email
Search with Google™:
    sdl •  sql •  delphi •  componenten •  cpp •  c


Pointers

Altijd een lastig onderwerp maar met de volgende weetjes kom je er wel uit;

  • Elke variabele wordt in het geheugen opgeslagen als een adres naar een geheugenplek
  • Iedere keer als je een variabele gebruikt wordt in het geheugen het adres opgehaald en op dit adres de inhoud van de variabele gelezen
  • Met pointers kun je achter het adres van de variabele komen
Tot zover de theorie, wat heb je er nu aan in de praktijk?

Een voorbeeld

Stel je hebt de volgende -foutieve- code (uiteraard zonder regelnummers):

[01] #include <stdio.h>
[02]
[03] void verdubbel( int x ){
[04]   x = x*2;
[05] }
[06]
[07] int main( void ){
[08]   int i;
[09]   i = 12;
[10]   printf("\n i = %d", i );
[11]   verdubbel( i );
[12]   printf("\n i*2 = %d", i );
[13]   return 0;
[14] }


De argeloze programmeur zal aannemen dat de printf functie in regel [10] 12 en in regel [12] 24 zal weergeven, immers in regel [11] wordt i in de functie verdubbel met twee vermenigvuldigd! De werkelijke uitvoer blijkt echter 12 en 12 te zijn. Hoe kan dat?

Dit heeft te maken met de 'scope' (geldigheid) van de variabele. Op het moment dat i in regel [08] aangemaakt wordt is deze lokaal in int main( void ) bekend. Buiten main (dus bijvoorbeeld in void verdubbel( int x ) is deze variabele niet bekend dus de functie kan er niks mee uithalen dat er voor zorgt dat het resultaat bewaard blijft.

In principe wordt er nu in de verdubbel functie een nieuwe variabele x aangemaakt (op een ander adres!); deze variabele x krijgt dezelfde waarde als i en wordt met 2 vermenigvuldigd en vervolgens wordt de functie afgesloten en is deze variabele niet meer bereikbaar. Er gebeurt dus helemaal niets met i! Hoe krijgen we het dan wel voor elkaar? Daar zijn pointers voor bedoeld.

Een beter voorbeeld

De werkwijze is nu als volgt. We weten dat de functie verdubbel uit de vorige listing geen idee had van het adres van i. Dat adres moeten we dus meegeven, immers als deze functie het adres van i weet kan het ook de inhoud lezen en bewerken! We weten dat we met pointers het adres van een variabele kunnen achterhalen en hoeven nu dus nog alleen maar te weten hoe je dit in C programmeerd. Onthoud daarvoor de volgende syntax:
  • het adres van een variabele achterhaal je met &
  • de inhoud van een adres achterhaal je met *
De volgende code past deze kennis toe om het programma wel goed te laten gaan.

[01] #include <stdio.h>
[02]
[03] void verdubbel( int *x ){
[04]   *x = *x * 2;
[05] }
[06]
[07] int main( void ){
[08]   int i;
[09]   i = 12;
[10]   printf("\n i = %d", i );
[11]   verdubbel( &i );
[12]   printf("\n i*2 = %d", i );
[13]   return 0;
[14] }


In deze code gebeurt het volgende; in regel [08] wordt gewoon een variabele aangemaakt (gedeclareerd) waarbij er dus in het geheugen een verwijzing naar een geheugenplek voor de inhoud van i gemaakt wordt. Het adres van deze plek kunnen we met &i achterhalen, de inhoud van i kunnen we met *i achterhalen. In regel [09] wordt de inhoud op 12 gezet. In regel [11] geven we het adres van i mee aan de functie verdubbel. De functie verdubbel kan met behulp van dit adres de inhoud van i ophalen en deze met behulp van regel [04] verdubbelen. In regel [04] staat eigenlijk; vermenigvuldig de inhoud van het adres wat we gekregen hebben met 2. Nu is de inhoud van i 24 geworden en zal dit getal ook door regel [12] weergegeven worden.

Pointers naar structures

Het verhaal wordt wat lastiger met structures. Een structure bevat velden en deze worden bij pointers anders benadert dan normaal. Ook hier moet je twee belangrijke dingen weten;
  • een veld in een structure benader je met .
  • een veld in een pointer naar een structure benader je met ->
De volgende listing geeft een voorbeeld van de voorgaande theorie:

[01] #include <stdio.h>
[02]
[03] struct coord2D{
[04]   int x;
[05]   int y;
[06] };
[07]
[08] void WisselXY( struct coord2D *Punt ){
[09]   int temp;
[10]   temp = Punt->x;
[11]   Punt->x = Punt->y;
[12]   Punt->y = temp;
[13] }
[14]
[15] int main( void ){
[16]   struct coord2D P1;
[17]   P1.x = 10;
[18]   P1.y = 5;
[19]   printf("\n Punt 1 = ( %d, %d )", P1.x, P1.y );
[20]   WisselXY( &P1 );
[21]   printf("\n Punt 1 = ( %d, %d )", P1.x, P1.y );
[22]  return 0;
[23]}


Het principe van het programma moet na het lezen van het eerste stuk over pointers duidelijk zijn. Het nieuwe vind je in regels [17] en [18] waar de normale manier met de punt gebruikt wordt om waarden aan de velden x en y van P1 toe te kennen.
In de functie die de pointer naar P1 heeft worden de waarden benaderd met ->. Het gebruik van een punt zou in dit geval tot de melding ' request for member `x' in `Punt', which is of non-aggregate type `coord2D *' leiden.

Pointers naar functies

Een belangrijk onderwerp bij pointers zijn de pointers naar functies. Dit gebruik is niet echt eenvoudig maar biedt hele leuke mogelijkheden. Je kunt op deze manier een event (gebeurtenis) op meerdere manier laten afhandelen afhankelijk van bepaalde omstandigheden. Dit klinkt theoretisch en vraagt om een voorbeeld. Het volgende programma vraagt om één parameter 'w' of 'windows' of 'l', 'linux' (zonder haakjes) en geeft aan de hand van de gegeven parameter een bepaald welkomstbericht.

[01] #include <stdio.h>
[02]
[03] typedef void (EenFunctie)( void );
[04] EenFunctie welkom_standaard;
[05] EenFunctie welkom_linux;
[06] EenFunctie welkom_windows;
[07]
[08] int main( int argc, char* argv[] ){
[09]  EenFunctie *DeFunctie;
[10]  DeFunctie = &welkom_standaard;
[11]  if ( argc>0 ){
[12]   if ( argv[1][0]=='l' ){ DeFunctie = &welkom_linux; }
[13]   else if ( argv[1][0]=='w' ){ DeFunctie = &welkom_windows; }
[14]  }
[15]  (*DeFunctie)();
[16]  return 0;
[17] }
[18]
[19] void welkom_standaard(void) { printf("\nHallo.. waarom gaf je geen geldige optie mee?");}
[20] void welkom_linux(void) { printf("\nHallo lieverd!!");}
[21] void welkom_windows(void) { printf("\nWordt het niet tijd voor een ECHT besturingssysteem?");}


In regel [03] wordt een algemeen type gemaakt zoals de functie er uit komt te zien. We willen een functie die geen parameters teruggeeft en die ook geen parameters verlangt dus void (EenFunctie)( void ).
In de regels [04] t/m [06] worden de drie functies alvast gedeclareerd (voorwaartse declaratie). Regel [08] geeft aan dat we geinteresseerd zijn in het aantal (argc) argumenten dat meegegeven wordt aan het programma. De argumenten komen in de array van pointers (voila.. daar is de pointer weer ;-) die in argv opgeslagen zijn.
In regel [09] wordt een pointer naar een functie van het type EenFunctie aangemaakt. In regel [10] geven we alvast aan dat de normale functie die uitgevoerd moet worden de functie 'welkom_standaard' is. We doen dit hier door aan de pointer 'DeFunctie' het adres (&) van de functie 'welkom_standaard' te geven!

In regels [12] en [13] wordt na wat controle (zijn er genoeg argumenten en begint het argument met een 'l' of een 'w') eventueel een andere functie aan 'DeFunctie' gehangen. In regel [15] wordt de functie daadwerkelijk uitgevoerd. Er was keuze uit drie functies en afhankelijk van de invoer wordt er één uitgevoerd omdat de pointer 'DeFunctie' naar één van de adressen van 'welkom_standaard' of 'welkom_linux' of 'welkom_windows' wijst.
In de regels [19] tot en met [21] worden de functies die vooraf gedeclareerd zijn helemaal uitgeschreven.



C code geschikt voor C++ maken

Je kunt in C er alvast rekening mee houden dat je functies ook in een C++ programma gebruikt kunnen worden door de volgende code in je header file toe te voegen;

1) voeg deze code helemaal aan het begin van je headerbestand toe;

#ifdef __cplusplus
extern "C" {
#endif

2) voeg deze code helemaal aan het eind van je headerbestand toe;

#ifdef __cplusplus
}
#endif


De code wordt nu voor C++ aangepast als de compiler een C++ compiler is (en dus de __cplusplus vlag gezet is).



Welke library voor welke functie?

Stel ik heb het volgende programma (test.c) geschreven;

#include

int main(){
 float i=pow(2,3);
 return 0;
}


Als ik dit probeer te bouwen dan krijg ik de volgende foutmelding;

delphiro@laptop:delphiro gcc -o eenTest test.c
...

/tmp/cc12nGni.o(.text+0x29): undefined reference to 'pow'
...
Dat is balen, het werkt niet en welke library moet ik dan wel hebben om het toch te kunnen bouwen? Onderneem de volgende stappen om die bibliotheek te achterhalen;

1 - compileer het programma

delphiro@laptop:delphiro gcc -c test.c

Je hebt nu de beschikking over de object file (test.o). Deze stap gaat wel goed omdat je code nu nog niet gelinkt wordt.

2 - bekijk de afhankelijkheden

Kijk met het volgende commando (inclusief spaties rond de U !) naar de objecten die het programma nodig heeft;

delphiro@laptop:delphiro nm test.o | grep ' U '

De uitvoer is in mijn geval:

  U pow

Alle bestanden die met een U (undefined) worden voorafgegaan moeten aan een library gekoppeld worden. Deze kun je opzoeken met 'ar' of 'nm'.

3 - zoek de juiste library

De standaard locatie van de libraries is /usr/lib maar er zijn er waarschijnlijk nog meer geplaatst in /usr/local/lib en wellicht nog op andere locaties. De eerste plek om te zoeken is echter /usr/lib. In ons voorbeeld is pow dus niet gekoppeld en zoeken we de bijbehorende library;

delphiro@laptop:delphiro cd /usr/lib
delphiro@laptop:delphiro nm -s librarynaam.a | grep 'pow in'


waarbij de librarynaam één van de vele namen in de directory kan zijn.

Uitleg;
nm geeft alle symbolen weer die in een object bestand zitten. Vervolgens wordt door 'grep' gezocht naar een string in de vorm van 'pow in'. De substring 'pow' spreekt voor zich, het stukje ' in' is geplaatst om allemaal onzinmeldingen te voorkomen; zodra ergens de tekst 'pow in blabla.o' staat weten we dat dit de juiste bibliotheek is. Nu hebben we nog het probleem dat er HEEL veel libraries zijn.

Hiervoor heb ik een (simpel) script geschreven dat ALLE libraries onder /usr/lib doorzoekt;

#!/bin/bash

#gebruikersinvoer
ZOEKDIR=/usr/lib
ZOEKTERM=pow

#programma
ZOEKTERM="$ZOEKTERM in"

echo "Zoeken naar $ZOEKTERM in de directory $ZOEKDIR."
for file in $( find $ZOEKDIR -name '*.a' | sort )
do
 echo "Bezig met bestand: $file"
 nm -s $file | grep "$ZOEKTERM"
done


Als je dit script met de juiste invoer (ZOEKDIR en ZOEKTERM) uitvoert verschijnen er meldingen in beeld zodra de term is gevonden. Bij mij verschijnt er bv.

...
Bezig met bestand: /usr/lib/libgcj.a
__ieee754_pow in e_pow.o
pow in w_pow.o
...
Bezig met bestand: /usr/lib/libm.a
__ieee754_pow in e_pow.o
pow in w_pow.o
...

4 - Bouw het programma nogmaals maar nu goed!

De functie is dus uitgewerkt in libgcj.a en libm.a. We kunnen deze nu toevoegen door de naam na lib bij het compileren als volgt toe te voegen.

delphiro@laptop:delphiro gcc -o eenTest test.c -lm
of
delphiro@laptop:delphiro gcc -o eenTest test.c -lgjc

In beide gevallen wordt het programma goed gebouwd.

NB Bij mij werkt dit allemaal prima.. maar ik draag uiteraard geen verantwoordelijkheid als dit bij anderen foutloopt. Ik heb het ook maar uitgezocht :-)