Berndt Wischnewski | Richard-Wagner-Str. 49 | 10585 Berlin | ||
Tel.: 030 - 3429075 | FAX : 030 34704037 | email: webmaster@peacesoftware.de | Ust-ID: DE135577379 |
C Kurs - wenn der Pointer in den Wald zeigtWas sind Pointer? Pointer sind Variablen und sie sind Adressen, es sind Variablen die eine Speicheradresse aufnehmen können, deshalb sind sie in 32 Bit Betriebssystemen auch 4 Bytes groß, entsprechen also unsigned long integer. Das Verständniß von Pointern ist wirklich wichtig um C richtig zu verstehen und programmieren zu können. Und zwar gleich aus drei Gründen:
Das waren die Vorteile, man kann mit Pointern aber auch sehr einfach die größten und undurchschaubarsten Fehler produzieren. Wenn Ihr Programm auf Ihrem Rechner fehlerfrei läuft, aber Ihr Kollege, Kunde, Bekannter behauptet steif und fest, es stürze bei ihm dauernd ab, dann suchen Sie den Fehler zuerst bei Ihren Pointern. Pointer, das Konzept dabei und die Umsetzung davon gehören zu den komplexesten Dingen beim C programmieren, also passen Sie gut auf, jetzt beim Kurs sowieso und natürlich auch später beim programmieren.
1. Deklaration und Benutzung von Pointern Jetzt wissen Sie, dass Pointer einerseits wichtig sind, andererseits aber ein Quell ständiger Freude bei der Fehlersuche darstellen. Nun sehen wir uns doch mal die Syntax zum Deklarieren und Benutzen von Pointern an: Bei der Deklaration einer Pointer-Typ Variable steht ein '*' vor dem Variablennamen. Das Sternchen '*' zeigt in diesem Fall an, dass es sich um einen Pointer handelt, es hat nichts mit der Multiplikation zu tun. Von allen Datentypen können auch Pointer deklariert werden,
Pointer ohne Datentyp: will man dem Pointer keinen bestimmten Datentyp zuweisen, deklariert man ihn als void. Die Größe eines generischen, nicht auf einen bestimmten Datentyp festgelegten Pointers entspricht der Größe einer Speicheradresse des Betriebssystems, 32-Bit -> 4 Bytes; 64-Bit -> 8 Bytes.
Der Pointer intptr enthält jetzt die Adresse der Variablen a, intptr referenziert a oder intptr zeigt auf a. Mit Sternchen '*' Operator erhält man das, was in der Speicheradresse steht, die der Pointer enthält, man dereferenziert den Pointer:
Und jetzt das ganze noch einmal als Graphik:
Nachtrag: seit kurzem nenne ich einen neuen Mac Pro mit 64 bit Betriebssystem mein eigen, da sind die Adressen 8 Byte groß ;) .
2. Pointer Arithmetik Speicheradressen sind auch nur Zahlen und mit Zahlen kann man rechnen, da jedoch beim Rechnen mit Adressen alles andere als Plus und Minus unsinnig erscheint, sind auch nur diese beiden Operationen erlaubt, warum solte jemand eine Adresse durch etwas teilen oder mit einer anderen Zahl multiplizieren? Alle Rechenoperationen außer Plus und Minus verursachen bei Pointern Fehlermeldungen. So nun rechnen wir doch mal was mit Pointern:
Wenn man den kleinen Test laufen läßt, erhält man folgenden Ausdruck auf dem Bildschirm: 1. Pointer: 420000 Zu dem ursprünglichen Wert der Pointervariablen wird nicht etwa 3 addiert, wie es eigentlich dasteht, sondern 3 mal dem Speicherbdarf des Pointertyps. Nochmal, erste Zeile, double *d; unser Pointer, *d zeigt auf den Datentyp double, double ist 8 Bytes groß. Wenn ich jetzt zu irgendeiner Adresse, z.B. 420000, die in meiner Pointervariablen d steht, 3 dazu addiere, erhalte ich als Ergebnis 420024, also 420000 plus 3 * 8, Ursprungsadresse plus 3 mal Größe des Datentyps. Das selbe funktioniert auch mit dem ++ oder -- Operator, ebenso += und -= . Bei Character Pointern, also Pointer die auf ein Zeichen zeigen, ist der Datentyp nur ein Byte groß, hier stimmt die Arithmetik dann sowieso wieder.
3. Pointer und Arrays Es gibt eine sehr enge Beziehung zwischen Pointern und Arrays, sie liegt darin begründet, dass Arrayvariablen eigentlich Pointer sind. Ein Array ist ein Pointer auf das erste Element des Array. Sie können Arrayvariablen direkt an einem Pointer zuweisen oder sie als Pointer an Funktionen übergeben. Hier mal ein kleines Beispielprogramm:
4. Verändern von an die Funktion übergebenen Variablen, Call bei Reference Bis jetzt hatten wir immer nur einfache Variablen an Funktionen übergeben, z.B so:
Man folgenden Ausdruck auf dem Bildschirm: inputvar: 420 Innerhalb von ' MeineLieblingsfunktion' wird die Eingangsvariable ' inputvar' verändert, aber nur lokal in der Funktion. Die Variable ' x' in der übergeordneten, aufrufenden Funktion hat anschließend immer noch den selben Wert wie vor dem Funktionsaufruf. Wie funktioniert das? Nun der C Compiler erzeugt eine Kopie der Eingangsvariablen. Die Funktion arbeitet nun mit dieser Kopie und kann damit machen wass sie will, die Variable in der aufrufenden Funktion bleibt unberührt. Diese Vorgehensweise nennt man auf gut deutsch Call By Value, der Wert der Variablen wird an eine Funktion übergeben, nicht die Variable selber. Meist ist dies auch das gewünschte Verhalten, trotzdem will oder muss man auch die an die Funktion übergeben Variable verändern können, nur wie? Nun, Sie übergeben einfach einen Pointer an die Funktion, dieser zeigt auf die echte Speicheradresse Ihrer Variablen und dann können Sie diese verändern:
Man folgenden Ausdruck auf dem Bildschirm: *inputvar: 420 Wie man sieht, wurde die Variable ' x' in der aurufenden Funktion verändert, die Adresse der Variablen x wird mit dem Kaufmannns-und in die Funktion 5. Pointer auf Pointer Um die Sache zu verkomplizieren, kann man sich auch Pointer deklarieren, welche auf einen anderen Pointer zeigen, wird z.B. häufig angewendet, wenn man eine Anwendung mit eigenem memory managment schreibt. Kurz nebenbei erklärt, man benutzt einen Pointer, der auf die Daten zeigt, werden die Daten im Speicher verschoben, wird dieser Pointer aktualisiert. Dann deklariert man sich einen Masterpointer, welcher auf diesen Datenpointer zeigt. Der Masterpointer verändert sich nicht, er zeigt immer nur auf den anderen Pointer, man kann ihn zu jeder Zeit an alle Funktionen übergeben. Um an die Daten zu kommen, muß man ihn nur zweimal dereferenzieren. Man deklariert einen Pointer auf einen Pointer mit zwei Sternchen '**' , hier mal ein Beispiel:
Man kann das natürlich noch weitertreiben und Pointer auf Pointer zeigen lassen, die wiederum auf Pointer auf Pointer zeigen:
ist zwar korrekter C Kode, bis jetzt hatte ich aber noch keine sinnvolle Anwendung für sowas gehabt.
6. Wenn der Pointer in den Wald zeigt Wie schon am Anfang erwähnt, kann man mit Pointern die undurchschaubarsten Fehler produzieren. ich zeige Ihnen jetzt wie das geht:
OK, man deklariert myptr , in diesem Moment steht entweder eine zufällige beliebige Adresse in myptr, der Pointer zeigt in den Wald, oder der Compiler initialisiert ihn mit NULL, in dem Fall zeigt er auf den Startbereich des Speichers, dorthin wo das Betriebsystem Ihres Computers anfängt. In der nächsten Zeile schreibt man an diese Speicheradresse einen Wert, '1234'. Wenn der Pointer wie im zweiten Fall auf NULL zeigt, bekommt man sofort eine 'NULL-POINTER-EXCEPTION' Fehlermeldung, d.h. das Betriebssystem beschwert sich, das ist noch einfach. Im ersten Fall, der Pointer zeigt einfach irgentwohin, können drei Sachen passieren:
Hier lässt sich jetzt auch dieses machmal gehts - manchmal gehts nicht Verhalten eines Programmes erklären. Die Adressen werden zur Laufzeit des Programmes zugewiesen, wenn der Rechner viel Speicherchips zur Verfügung hat oder der Speicher nur wenig ausgenutzt wird, sprich nur wenige Programme laufen, wird die Chance größer, das der Pointer auf freien Speicherplatz zeigt und das Programm erstmal problemlos läuft. Wird es dann von jemand andrem auf einem Rechner mit wenig freien Speicher benutzt, kann es sein das der nicht initialisierte Pointer dann auf etwas wichtiges zeigt und sich das Programm unsinnig verhält oder abstürzt. Besser ist jetzt folgende Konstruktion:
Die meisten modernen C Compiler setzen Pointervariablen erstmal auf NULL, schauen Sie sich jetzt aber mal folgendes Codefragment an:
Sie haben es bestimmt verstanden. Der Integerarray hat nur 10 Element und der Pointer wird, ausgehend von der Adresse des 5. Elementes des Array, auf eine Adresse die 400 Bytes größer ist, gesetzt. 400 Bytes, weil integer 4 Byte groß ist, mal 100. Der Pointer zeigt wieder mal in den Wald, diesmal ist die Chance das er noch ins gleiche Segment zeigt, sogar recht groß. Wahrscheinlich gibt es keinen SEGMENT-FAULT, was an der Speicherstelle, auf die der Pointer jetzt zeigt, steht, ist wie vorher dem Zufall überlassen.
Ein weiterer netter Fehler ist es den Pointer auf freigegebenen Speicherplatz zeigen zu lassen:
Hier passiert folgendes, man legt sich einen Pointer an: int *ptr = NULL; Das Problem ist bloß, dass die lokale Variable localx nach dem Verlassen der Funktion SomeFunction gar nicht mehr existiert, der Speicherplatz der lokalen Variablen der Funktion wird nach Beenden der Funktion wieder freigegeben. Diesen Fehler nennt man einen 'dangling pointer' oder 'hängenden Pointer' . Ob der Speicherplatz schon wieder benutzt wurde oder nicht ist jetzt wieder dem Zufall überlassen, d.h. das Programm hat einen Fehler, kann aber machmal doch funktionieren. Also immer daran denken "double check your damn pointers".
|
Alles über Pointer: |