Zeiger (Informatik)
Ein Zeiger oder Pointer bezeichnet in der Informatik eine spezielle Klasse von Variablen, die auf einen anderen Speicherbereich verweisen. Der referenzierte Speicherbereich enthält entweder Daten (Objekt, Variable) oder Programmcode. Zeiger auf Programmcode werden meistens als Funktionszeiger bezeichnet.
Man unterscheidet bei Zeigern zwischen zwei Zugriffsverfahren:
- Auf der einen Seite lässt sich auf den Wert zugreifen, der auf das referenzierte Element (Datenbereich oder Code-Abschnitt) verweist, man spricht auch von der Adresse.
- Auf der anderen Seite kann auf das verwiesene Element selbst zugegriffen werden. Die Operation, bei der man über den Zeiger auf das verwiesene Element zugreift, nennt man Dereferenzierung. Funktionszeiger kommen häufig in Verbindung mit Rückruffunktionen (callback function) zum Einsatz und stellen eine Form der späten Bindung dar.
Ein verbreitetes Anwendungsgebiet für Zeiger ist die Referenzierung dynamisch angeforderten Speichers. Auch bestimmte Datenstrukturen, wie z. B. verkettete Listen werden in der Regel mit Hilfe von Zeigern implementiert.
Zeiger in Programmiersprachen
Zeiger kommen vor allem in maschinennahen Programmiersprachen wie z. B. Assembler, C oder C++ vor, während man den Gebrauch in streng typisierten Sprachen wie Modula-2 oder Ada stark einschränkt und sie in Sprachen wie Java oder Eiffel zwar intern vorhanden, aber für den Programmierer vollständig verborgen (opak) sind. Mit erstgenannten Sprachen ist es möglich, Zeiger auf beliebige Stellen im Speicher zu erzeugen oder mit ihnen zu rechnen.
Manche Programmiersprachen schränken den Gebrauch von Zeigern ein, weil Programmierern bei der Arbeit mit Zeigern leicht schwerwiegende Programmierfehler unterlaufen (sicherlich die Hauptursache für Pufferüberläufe und Abstürze bei zum Beispiel in C und C++ geschriebenen Programmen).
Typisierte Zeiger
In den meisten Programmiersprachen werden Zeiger direkt mit Datentypen assoziiert. So kann ein so genannter "Zeiger auf ein Objekt vom Typ Integer" normalerweise auch nur auf ein Objekt vom Typ "Integer" verweisen. Der Datentyp des Zeigers selbst bestimmt sich also durch den Typ, auf den er verweist. In der Programmiersprache C ist dies eine Voraussetzung zur Realisierung der Zeigerarithmetik, denn nur durch das Wissen um die Speichergröße des assoziierten Typs kann die Adresse des Vorgänger- oder Nachfolgeelementes berechnet werden. Darüber hinaus ermöglicht die Typisierung von Zeigern dem Compiler, Verletzungen der Typkompatibilität zu erkennen. üoijmüopijüoij
Untypisierte Zeiger
Diese Zeiger sind mit keinem Datentyp verbunden. Sie können nicht dereferenziert, inkrementiert oder dekrementiert werden.
Leerzeiger (Nullzeiger)
Der Leerzeiger ist ein Zeiger mit einem speziellen, dafür reservierten Wert (nicht zwingend numerisch 0), der anzeigt, dass auf nichts verwiesen wird. Nullzeiger werden in fast allen Sprachen sehr häufig verwendet, da man mittels des Nullzeigers eine "designierte Leerstelle" kennzeichnet. Zum Beispiel wird eine einfach verkettete Liste meist so implementiert, dass das letzte Element auf den Leerzeiger als Folgeelement verweist. Er kennzeichnet in diesem Fall also das Ende der Liste. In Pascal-basierten Sprachen wie Delphi bzw. Object Pascal heißt der Nullzeiger beispielsweise nil (lateinisch: "nichts" oder Akronym für "not in list"), in C und C++ NULL.
Intern werden Nullzeiger auf unterschiedliche Arten repräsentiert, weshalb man sich nach Möglichkeit nie um den tatsächlichen Wert kümmert, sondern ihn einfach als gegebene Leerstelle akzeptiert, die keinen benutzbaren Inhalt trägt. Die logische Folge ist, dass ein Leerzeiger nicht dereferenziert werden darf. Ein solcher Umgang mit dem Nullzeiger kann undefiniertes Verhalten hervorrufen.
Uninitialisierte Zeiger
Falls eine Zeigervariable dereferenziert wird, die nicht auf einen gültigen Speicherbereich des entsprechenden Typs zeigt, kann es ebenfalls zu unerwartetem Verhalten kommen. So eine Situation kann auftreten, wenn eine Variable vor ihrer Benutzung nicht auf einen gültigen Wert initialisiert wurde oder wenn sie noch auf eine Speicheradresse verweist, die nicht mehr gültig ist (wilder Zeiger).
Eigenschaften
Vorteile
Die Verwendung von Zeigern kann in bestimmten Fällen den Programmablauf beschleunigen oder helfen, Speicherplatz zu sparen:
- Ist die von einem Programm im Speicher zu haltende Datenmenge am Programmstart unbekannt, so kann genau so viel Speicher alloziert werden, wie benötigt wird (Dynamische Speicherverwaltung).
- Bei der Verwendung von Feldern bzw. Vektoren kann man mittels Zeigern schnell innerhalb des Feldes springen und navigieren. Mittels Zeigerinkrement wird dabei durch ein Feld hindurchgelaufen. Anstatt einen Index zu verwenden und so die Feldelemente über diesen anzusprechen, setzt man zu Beginn des Ablaufs einen Zeiger auf den Anfang des Feldes und inkrementiert diesen Zeiger bei jedem Durchlauf. Diese Art des Zugriffs auf Felder wird in vielen Programmiersprachen und Compilern an manchen Stellen intern automatisch so umgesetzt.
- Verweise auf Speicherbereiche können geändert werden, z. B. zur Sortierung von Listen, ohne die Elemente umkopieren zu müssen (dynamische Datenstrukturen).
- Bei Funktionsaufrufen kann durch die Übergabe eines Zeigers auf eine Variable vermieden werden, die Variable selbst zu übergeben, was eine in bestimmten Fällen sehr zeitaufwändige Anfertigung einer Kopie der Variablen erfordern würde.
- Anstatt Variablen jedes Mal zu kopieren und so jedes Mal erneut Speicherplatz zur Verfügung zu stellen, kann man in manchen Fällen einfach mehrere Zeiger auf ein und dieselbe Variable verweisen lassen.
Nachteile und Gefahren
Es gibt Sprachen, die bewusst auf den Einsatz von Zeigern verzichten (s. o.). Dies hat vor allem folgende Gründe:
- Der Umgang mit Zeigern ist schwierig zu erlernen, kompliziert und fehleranfällig. Vor allem im Sinne von Zeigern zu denken, bereitet Programmieranfängern anfangs oft Schwierigkeiten. Auch bei erfahrenen Programmierern kommen Flüchtigkeitsfehler im Umgang mit Zeigern noch relativ häufig vor.
- Programmierfehler bei der Arbeit mit Zeigern können schwere Folgen haben. So kommt es z. B. zu Programmabstürzen, unbemerkter Beschädigung von Daten, Pufferüberläufen oder "verlorenen" Speicherbereichen (so genannten [[Arrays zu verwenden, weil diese eine kompakte Darstellung im Speicher haben.
- Letzteres kann sich auch negativ im Zusammenhang mit Paging auswirken.liuhpi9uhpiuoinoinmoikn
Zeigeroperationen
- Dereferenzieren auf das Objekt (die Variable), auf welches der Zeiger zeigt, zugreifen
- Inkrementieren/Dekrementieren den Zeiger auf das nächste/vorhergehende Objekt (Variable) versetzten
- Erzeugen/Zerstören des referenzierten Objektes
- Vergleichen mit anderen Zeigern oder mit NULL
- Subtrahieren zwei Zeiger dürfen subtrahiert werden. Das Ergebnis ist eine Ganzzahl.
- Addieren diese Operation ist verboten, da sie keinen Sinn macht!
Zeigerarithmetik
Das Erhöhen oder Erniedrigen eines Zeigers um einen festen Wert oder das Subtrahieren zweier Zeiger wird als Zeigerarithmetik bezeichnet.
Intelligente Zeiger
Als Intelligente Zeiger (smart pointers) werden Objekte bezeichnet, die sich wie Zeiger verhalten und verwenden lassen, aber intelligenter sind als einfache Zeiger. Durch Kapselung des Zeigers innerhalb eines Objektes wird versucht, bestimmte negative Eigenschaften von Zeigern zu verhindern.
Zeiger auf eine COM- oder Corba-Schnittstelle und sind unter manchen Programmiersprachen (z. B. Delphi) als Intelligenter Zeiger implementiert.
Funktionszeiger (Methodenzeiger)
Funktionszeiger bilden eine besondere Klasse von Zeigern. Sie zeigen nicht auf Daten, sondern auf den Einsprungspunkt einer Funktion. Damit ist es möglich, benutzerdefinierte Funktionsaufrufe, deren Ziel erst zur Laufzeit bestimmt wird, zu realisieren. Häufigster Anwendungsfall sind Rückruffunktionen.
Siehe auch: Methodenzeiger