Zeiger (Computerprogrammierung)
Ich denke, ich denke Zuweisungsanweisungen und Zeigervariablen, die zu den "wertvollsten Schätzen der Informatik" gehören.
Donald Knuth, Strukturierte Programmierung mit Aussagen[1]

Im Informatik, a Zeiger ist ein Objekt in vielen Programmiersprachen das speichert a Speicheradresse. Dies kann der eines anderen Wertes sein, der sich befindet Computerspeicheroder in einigen Fällen das von Speichermaked Computerhardware. Ein Zeiger Verweise Eine Position im Speicher und das Erhalten des an diesem Ort gespeicherten Wert Derference der Zeiger. Als Analogie könnte eine Seitenzahl im Index eines Buches als Zeiger auf die entsprechende Seite angesehen werden. Dereferencing ein solcher Zeiger würde durchgeführt, indem Sie mit der angegebenen Seitennummer zur Seite umdrehen und den auf dieser Seite gefundenen Text lesen. Das tatsächliche Format und der Inhalt einer Zeigervariablen sind vom zugrunde liegenden Abhängigen abhängig Rechnerarchitektur.
Die Verwendung von Zeigern verbessert sich erheblich Leistung Für sich wiederholende Operationen wie das Durchqueren wiederholbar Daten Strukturen (z.B. Saiten, Nachschlagetabellen, Steuertabellen und Baum Strukturen). Insbesondere ist es in Zeit und Raum oft viel billiger, Zeiger zu kopieren und zu kopieren, als auf die Daten zu kopieren und zuzugreifen, auf die die Zeiger zeigen.
Zeiger werden auch verwendet, um die Adressen von Einstiegspunkten für zu halten genannt Unterroutinen in Verfahrensprogrammierung und für die Laufzeitverknüpfung mit Dynamische Linkbibliotheken (DLLs). Im Objekt orientierte Programmierung, Zeiger auf Funktionen werden für Bindung Methodenoft verwenden Virtuelle Methodentabellen.
Ein Zeiger ist eine einfache, konkretere Implementierung der Abstracter Hinweis Datentyp. Besonders mehrere Sprachen, Sprachen auf niedriger EbeneUnterstützen Sie einen Zeiger, obwohl einige mehr Einschränkungen für ihre Verwendung haben als andere. Während "Zeiger" verwendet wurde, um sich auf Referenzen im Allgemeinen zu beziehen, gilt er besser auf Datenstrukturen Deren Schnittstelle ermöglicht es explizit, dass der Zeiger manipuliert wird (arithmetisch über Zeigerarithmetik) als Speicheradresse im Gegensatz zu a Magic Cookie oder Fähigkeit was solche nicht erlaubt. Da Zeiger sowohl einen geschützten als auch den ungeschützten Zugriff auf Speicheradressen ermöglichen, sind mit ihnen Risiken verbunden, insbesondere im letzteren Fall. Primitive Zeiger werden oft in einem ähnlichen Format wie einem gespeichert ganze Zahl; Der Versuch, einen solchen Zeiger zu "nachschlagen", dessen Wert keine gültige Speicheradresse ist, kann jedoch ein Programm dazu führen Absturz (oder ungültige Daten enthalten). Dieses potenzielle Problem zu lindern, als eine Frage von Geben Sie Sicherheit einZeiger werden als separates Typ, auf die sie hinweisen, als separates Parametrisierte angesehen, auch wenn die zugrunde liegende Darstellung eine Ganzzahl ist. Andere Maßnahmen können ebenfalls ergriffen werden (wie z. Validierung & Grenzenprüfung), um zu überprüfen, ob die Zeigervariable einen Wert enthält, der sowohl eine gültige Speicheradresse als auch innerhalb des numerischen Bereichs ist, den der Prozessor adressieren kann.
Geschichte
1955 der ukrainische Informatiker 1955 Kateryna Yushchenko erfand die Programmiersprache ansprechen Dies machte mögliche indirekte Adressierung und Adressen des höchsten Ranges - analog zu Zeigern. Diese Sprache wurde auf den Computern der Sowjetunion häufig verwendet. Es war jedoch außerhalb der Sowjetunion und normalerweise unbekannt Harold Lawson wird 1964 der Erfindung des Zeigers zugeschrieben.[2] Im Jahr 2000 wurde Lawson vom Computer Pioneer Award von der verliehen IEEE "[f] oder Erfindung der Zeigervariablen und der Einführung dieses Konzepts in PL/I, wodurch zum ersten Mal die Fähigkeit zur flexiblen Behandlung verknüpfter Listen in einer allgemeinen Sprache auf hoher Ebene flexibel behandelt wird."[3] Sein wegweisendes Papier über die Konzepte erschien in der Juni 1967 -Ausgabe von CACM mit dem Titel: PL/I List Processing. Laut dem Oxford Englisch Wörterbuch, das Wort Zeiger erschien zuerst in gedruckter Form als Stapelzeiger in einem technischen Memorandum von der Systementwicklungsgesellschaft.
Formelle Beschreibung
Im Informatik, ein Zeiger ist eine Art Art von Hinweis.
A Daten primitiv (oder nur Primitive) ist jedes Daten, das gelesen oder geschrieben werden kann Computerspeicher Verwenden eines Speicherzugriffs (zum Beispiel beides a Byte und ein Wort sind Primitive).
A Datenaggregat (oder nur Aggregat) ist eine Gruppe von Primitiven, die sind logisch In dem Speicher zusammenhängend und diese werden gemeinsam als ein Datum betrachtet (z. B. könnte ein Aggregat 3 logisch zusammenhängende Bytes sein, deren Werte die 3 Koordinaten eines Punktes im Raum darstellen). Wenn ein Aggregat vollständig aus derselben Primitiventyp besteht, kann das Aggregat als ein bezeichnet werden Array; In gewissem Sinne ein Multi-Byte Wort Primitive ist eine Reihe von Bytes, und einige Programme verwenden auf diese Weise Wörter.
Im Kontext dieser Definitionen a Byte ist der kleinste Primitive; jeder Speicheradresse Gibt ein anderes Byte an. Die Speicheradresse des anfänglichen Bytes eines Datums wird als Speicheradresse (oder angesehen Grundspeicheradresse) des gesamten Datums.
A Speicherzeiger (oder nur Zeiger) ist ein Primitiv, dessen Wert als Speicheradresse verwendet werden soll; es wurde gesagt, dass Ein Zeiger zeigt auf eine Speicheradresse. Es wird auch gesagt, dass Ein Zeiger zeigt auf ein Datum [im Speicher] Wenn der Wert des Zeigers die Speicheradresse des Daten ist.
Allgemeiner ist ein Zeiger eine Art Art von Hinweisund es wird gesagt, dass Ein Zeiger verweist ein Datum, das irgendwo im Speicher gespeichert ist; Um dieses Datum zu erhalten, ist Der Zeiger Dereference. Das Merkmal, das Zeiger von anderen Arten von Referenz trennt, ist, dass der Wert eines Zeigers als Speicheradresse interpretiert werden soll, was ein ziemlich niedriges Konzept ist.
Referenzen dienen als Indirektionsstufe: Der Wert eines Zeigers bestimmt, welche Speicheradresse (dh welches Datum) in einer Berechnung verwendet werden soll. Da Indirektion ein grundlegender Aspekt von Algorithmen ist, werden Zeiger oft als grundlegend ausgedrückt Datentyp in Programmiersprachen; in statisch (oder stark) Typisierte Programmiersprachen, die, die Typ eines Zeigers bestimmt den Typ des Datums, auf den der Zeiger zeigt.
Architekturwurzeln
Zeiger sind sehr dünn Abstraktion Zusätzlich zu den Adressierungsfunktionen der meisten modernen Architekturen. Im einfachsten Schema ein die Anschrift, oder ein numerischer Index, wird jeder Speichereinheit im System zugeordnet, wobei das Gerät normalerweise entweder a ist Byte oder ein Wort - je nachdem, ob die Architektur ist Byteadressibel oder Wortadressibel - effektiv das gesamte Gedächtnis in eine sehr große verwandeln Array. Das System würde dann auch einen Betrieb zur Verfügung stellen, um den in der Speichereinheit gespeicherten Wert an einer bestimmten Adresse abzurufen (normalerweise mit der Maschine verwendet Allgemeine Register).
Im üblichen Fall ist ein Zeiger groß genug, um mehr Adressen zu halten, als es im System Speichereinheiten gibt. Dies führt die Möglichkeit ein, dass ein Programm versucht, auf eine Adresse zuzugreifen, die keiner Speichereinheit entspricht, entweder weil nicht genügend Speicher installiert ist (d. H. Über den Bereich des verfügbaren Speichers hinaus) oder die Architektur unterstützt solche Adressen nicht. Der erste Fall kann in bestimmten Plattformen wie dem Intel x86 Architektur, genannt werden Segmentierungsfehler (Segfault). Der zweite Fall ist in der aktuellen Implementierung von möglich AMD64, wo Zeiger 64 -Bit lang sind und sich nur auf 48 Bit erstrecken. Die Zeiger müssen bestimmten Regeln (kanonische Adressen) entsprechen. Wenn also ein nicht-kanonischer Zeiger derenferenziert ist, erhebt der Prozessor a allgemeine Schutzverletzung.
Andererseits haben einige Systeme mehr Speichereinheiten als Adressen. In diesem Fall ein komplexeres Schema wie z. Speichersegmentierung oder Paging wird verwendet, um verschiedene Teile des Speichers zu unterschiedlichen Zeiten zu verwenden. Die letzten Inkarnationen der X86-Architektur unterstützen bis zu 36 Bit physischer Speicheradressen, die dem 32-Bit-linearen Adressraum durch die zugeordnet wurden Pae Paging -Mechanismus. Somit kann jeweils nur 1/16 des möglichen Gesamtspeichers zugegriffen werden. Ein weiteres Beispiel in derselben Computerfamilie war der 16-Bit Sicherheitsmodus des 80286 Der Prozessor, der zwar nur 16 MB physischen Speicher unterstützt, könnte jedoch bis zu 1 GB virtuelles Speicher zugreifen, aber die Kombination von 16-Bit-Adressen- und Segmentregistern, die auf mehr als 64 kb in einer Datenstruktur umständlich zugänglich gemacht wurden.
Um eine konsistente Schnittstelle bereitzustellen, bieten einige Architekturen Speicher-abgebildete I/O, was es einigen Adressen ermöglicht, sich auf Speichereinheiten zu beziehen, während sich andere beziehen Geräteregister anderer Geräte im Computer. Es gibt analoge Konzepte wie Datei -Offsets, Array -Indizes und Remote -Objektreferenzen, die einige der gleichen Zwecke wie Adressen für andere Arten von Objekten dienen.
Verwendet
Zeiger werden direkt ohne Einschränkungen in Sprachen wie z. Pl/i, C, C ++, Pascal, Freilasischund implizit in den meisten Assemblersprachen. Sie werden hauptsächlich zum Konstruktion verwendet Verweise, was wiederum für den Bau fast alle von grundlegender Bedeutung ist Datenstrukturensowie über die Übergabe von Daten zwischen verschiedenen Teilen eines Programms.
In funktionalen Programmiersprachen, die stark auf Listen beruhen, werden Datenreferenzen abstrakt verwaltet, indem primitive Konstrukte wie verwendet werden Nachteile und die entsprechenden Elemente Auto und Cdr, was als spezialisierte Zeiger auf die ersten und zweiten Komponenten einer Konsumzelle angesehen werden kann. Dies führt zu einigen idiomatischen "Geschmack" der funktionellen Programmierung. Durch Strukturieren von Daten in solchen Conslisten, diese Sprachen erleichtern rekursiv Mittel zum Erstellen und Verarbeiten von Daten - zum Beispiel durch rekursives Zugriff auf Kopf- und Schwanzelemente von Listenlisten; z.B. "Das Auto des CDR des CDR nehmen". Im Gegensatz dazu die Speicherverwaltung basierend auf Zeiger Dereferenzieren in einer Annäherung an einen Array von Speicheradressen erleichtert die Behandlung von Variablen als Slots, in die Daten zugewiesen werden können unbedingt.
Beim Umgang mit Arrays das Kritische Sieh nach oben Der Betrieb beinhaltet typischerweise eine Stufe namens namens Adressberechnung Dies beinhaltet das Erstellen eines Zeigers auf das gewünschte Datenelement im Array. In anderen Datenstrukturen, wie z. verlinkte ListenZeiger werden als Verweise verwendet, um ein Stück der Struktur explizit an einen anderen zu binden.
Zeiger werden verwendet, um Parameter durch Bezugnahme zu übergeben. Dies ist nützlich, wenn der Programmierer die Änderungen einer Funktion an einem Parameter für den Anrufer der Funktion sichtbar ist. Dies ist auch nützlich, um mehrere Werte aus einer Funktion zurückzugeben.
Zeiger können auch daran gewöhnt werden zuweisen und deaktiviert dynamische Variablen und Arrays im Speicher. Da eine Variable oft überflüssig wird, nachdem sie ihren Zweck erfüllt hat, ist es eine Erinnerungsverschwendung, sie zu behalten, und daher ist es eine gute Praxis, sie zu bearbeiten (unter Verwendung der ursprünglichen Zeigerreferenz), wenn sie nicht mehr benötigt wird. Wenn dies nicht der Fall ist, kann dies zu einem führen Speicherleck (Wenn der freie Speicher allmählich oder in schweren Fällen schnell verfügbar ist, verringert sich aufgrund der Ansammlung zahlreicher redundanter Speicherblöcke).
C Zeiger
Das Basis Syntax Um einen Zeiger zu definieren, ist:[4]
int *ptr;
Dies erklärt ptr
Als Kennung eines Objekts des folgenden Typs:
- Zeiger, der auf ein Objekt vom Typ zeigt
int
Dies wird normalerweise prägnanter als "angegeben"ptr
ist ein Zeiger auf int
. "
Da die C -Sprache keine implizite Initialisierung für Objekte der automatischen Speicherdauer festlegt,[5] Es sollte häufig darauf geachtet werden, dass die Adresse, an die ptr
Punkte sind gültig; Aus diesem Grund wird manchmal vermutet, dass ein Zeiger explizit in die initialisiert wird Null Zeiger Wert, der traditionell in C mit dem standardisierten Makro spezifiziert wird NULL
:[6]
int *ptr = NULL;
Dereferenzieren ein Nullzeiger in C produziert undefiniertes Verhalten,[7] das könnte katastrophal sein. Die meisten Implementierungen stoppen jedoch einfach die Ausführung des betreffenden Programms, normalerweise mit a Segmentierungsfehler.
Das Initialisieren von Zeigern kann jedoch die Programmanalyse beeinträchtigen und damit Fehler verbergen.
Wenn ein Zeiger deklariert wurde, ist der nächste logische Schritt auf jeden Fall, dass er auf etwas verweist:
int a = 5; int *ptr = NULL; ptr = &a;
Dies weist den Wert der Adresse von zu a
zu ptr
. Zum Beispiel wenn a
wird am Speicherort von 0x8130 gespeichert, dann der Wert von ptr
wird 0x8130 nach der Aufgabe sein. Um den Zeiger zu Dereference zu erhalten, wird erneut ein Sternchen verwendet:
*ptr = 8;
Dies bedeutet, den Inhalt von zu nehmen ptr
(Dies ist 0x8130), "lokalisieren" diese Adresse im Speicher und setzen a
Es wird später wieder zugegriffen, der neue Wert wird 8 sein.
Dieses Beispiel kann klarer sein, wenn der Speicher direkt untersucht wird. Annehmen, dass a
befindet sich unter der Adresse 0x8130 im Speicher und ptr
bei 0x8134; Nehmen wir auch an, dies ist eine 32-Bit-Maschine, sodass ein int 32-bit breit ist. Das Folgende ist, was im Speicher sein würde, nachdem der folgende Code -Snippet ausgeführt wurde:
int a = 5; int *ptr = NULL;
Adresse Inhalt 0x8130 0x00000005 0x8134 0x00000000
(Der hier gezeigte Nullzeiger ist 0x00000000.) Durch Zuweisen der Adresse von a
zu ptr
:
ptr = &a;
ergibt die folgenden Speicherwerte:
Adresse Inhalt 0x8130 0x00000005 0x8134 0x00008130
Dann durch Dereferenzierung ptr
Durch Codierung:
*ptr = 8;
Der Computer nimmt den Inhalt von an ptr
(Dies ist 0x8130), "lokalisieren" diese Adresse und zuweisen 8 diesem Ort, der den folgenden Speicher ergibt:
Adresse Inhalt 0x8130 0x00000008 0x8134 0x00008130
Klar zugreifen a
ergibt den Wert von 8, da der vorherige Befehl den Inhalt von geändert hat a
durch den Zeiger ptr
.
Verwendung in Datenstrukturen
Beim Einrichten Datenstrukturen wie Listen, Warteschlangen Und Bäume müssen Zeiger haben, um zu verwalten, wie die Struktur implementiert und kontrolliert wird. Typische Beispiele für Zeiger sind Startzeiger, Endzeiger und Stapel Zeiger. Diese Zeiger können entweder sein absolut (das tatsächliche physikalische Adresse oder ein virtuelle Adresse in virtueller Speicher) oder relativ (ein Offset Aus einer absoluten Startadresse ("Basis"), die normalerweise weniger Bits als eine vollständige Adresse verwendet, benötigt jedoch normalerweise einen zusätzlichen arithmetischen Betrieb, um es zu lösen).
Relative Adressen sind eine Form des Handbuchs Speichersegmentierungund teilen Sie viele seiner Vor- und Nachteile. Ein Zwei-Byte-Offset, der eine 16-Bit-Ganzzahl enthält, kann verwendet werden, um eine relative Adressierung für bis zu 64 zu liefern Kib (216 Bytes) einer Datenstruktur. Dies kann leicht auf 128, 256 oder 512 KIB ausgedehnt werden ausgerichtet an einer Halbwort-, Wort- oder Doppelwortgrenze (jedoch eine zusätzliche "Verschiebung links" erfordert Bitgewise Operation- 1, 2 oder 3 Bits - In der Zeit, um den Offset um den Faktor 2, 4 oder 8 vor der Ergänzung zur Basisadresse anzupassen). Im Allgemeinen sind solche Schemata jedoch viel Ärger und aus Gründen des Programmierers absolute Adressen (und das zugrunde liegende, a Flat Adressraum) Ist bevorzugt.
Ein One -Byte -Offset wie das Hexadezimal ASCII Der Wert eines Zeichens (z. B. x'29 ') kann verwendet werden, um auf einen alternativen Ganzzahlwert (oder Index) in einem Array (z. B. x'01') zu verweisen. Auf diese Weise können Charaktere sehr effizient übersetzt werden 'Rohdaten'zu einem verwendbaren Sequenzial Index und dann zu einer absoluten Adresse ohne a Nachschlagwerk.
C Arrays
In C wird die Array -Indexierung offiziell in Bezug auf die Zeigerarithmetik definiert. Das heißt, die Sprachspezifikation erfordert das Array [i]
äquivalent sein *(Array + i)
.[8] So können Arrays in C als Zeiger auf aufeinanderfolgende Gedächtnisbereiche (ohne Lücken) betrachtet werden.[8] und die Syntax für den Zugriff auf Arrays ist identisch für das, was zur Derereferenzzeiger verwendet werden kann. Zum Beispiel ein Array Array
kann auf folgende Weise deklariert und verwendet werden:
int Array[5]; / * Deklariert 5 angrenzende ganze Zahlen */ int *ptr = Array; / * Arrays können als Zeiger verwendet werden */ ptr[0] = 1; / * Zeiger können mit Array -Syntax */indiziert werden *(Array + 1) = 2; / * Arrays können mit Zeigersyntax */Derferenziert werden *(1 + Array) = 2; / * Zeiger Addition ist kommutativ */ Array[2] = 4; / * Einweisbetreiber ist kommutativ */
Dies zuteilt einen Block von fünf Ganzzahlen und nennt den Block Array
, was als Zeiger auf den Block fungiert. Eine weitere häufige Verwendung von Zeigern besteht darin, auf den dynamisch zugewiesenen Speicher von zu verweisen Malloc Dies gibt einen aufeinanderfolgenden Speicherblock für nicht weniger als die angeforderte Größe zurück, die als Array verwendet werden kann.
Während die meisten Operatoren auf Arrays und Zeiger gleichwertig sind, ist das Ergebnis der Größe von
Operator unterscheidet sich. In diesem Beispiel, Größe (Array)
wird bewerten zu 5*sizeof (int)
(die Größe des Arrays), während Größe (PTR)
wird bewerten zu Größe (int*)
, die Größe des Zeigers selbst.
Standardwerte eines Arrays können deklariert werden wie:
int Array[5] = {2, 4, 3, 1, 5};
Wenn Array
befindet sich im Speicher ab der Adresse 0x1000 auf einem 32-Bit Little-Endian Maschine dann enthält der Speicher die folgenden (Werte sind in hexadezimal, wie die Adressen):
0 1 2 3 1000 2 0 0 0 1004 4 0 0 0 1008 3 0 0 0 100c 1 0 0 0 1010 5 0 0 0
Hier sind fünf ganze Zahlen dargestellt: 2, 4, 3, 1 und 5. Diese fünf Ganzzahlen belegen jeweils 32 Bit (4 Bytes) mit dem am wenigsten signifikanten Byte, der zuerst gespeichert ist CPU -Architektur) und werden nacheinander gespeichert mit der Adresse 0x1000.
Die Syntax für C mit Zeigern ist:
-
Array
bedeutet 0x1000; -
Array + 1
bedeutet 0x1004: Das "+ 1" bedeutet, die Größe von 1 hinzuzufügenint
, was 4 Bytes sind; -
*Array
Mittel, um den Inhalt vonArray
. Betrachten Sie den Inhalt als Speicheradresse (0x1000), suchen Sie den Wert an diesem Ort (0x0002). -
Array [i]
bedeutet Elementnummeri
, 0 basierend, vonArray
was in übersetzt wird*(Array + i)
.
Das letzte Beispiel ist, wie man auf den Inhalt von zugreift Array
. Brechen sie ab:
-
Array + i
ist der Speicherort des (i)th Element vonArray
, beginnend bei i = 0; -
*(Array + i)
Nimmt diese Speicheradresse und Dereferen sie, um auf den Wert zuzugreifen.
C verknüpfte Liste
Unten finden Sie eine Beispieldefinition von a verlinkte Liste in C.
/* Die leere verlinkte Liste wird von NULL dargestellt * oder ein anderer Sentinel -Wert */ #define leere_list null Struktur Verknüpfung { Leere *Daten; / * Daten dieses Links */ Struktur Verknüpfung *nächste; /* Nächster Link; Leere_list, wenn es keine gibt */ };
Diese Zeiger-rezisive Definition ist im Wesentlichen die gleiche wie die Referenzrezisivdefinition von der Haskell -Programmiersprache:
Daten Verknüpfung a = Null | Nachteile a (Verknüpfung a)
Null
ist die leere Liste und Nachteile A (Link a)
ist ein Nachteile Zelle vom Typ a
mit einem anderen Link auch vom Typ a
.
Die Definition mit Referenzen ist jedoch vom Typ überprüft und verwendet keine potenziell verwirrenden Signalwerte. Aus diesem Grund werden Datenstrukturen in C normalerweise über via behandelt Wrapper -Funktionen, die sorgfältig auf Korrektheit überprüft werden.
Pass-by-Address mit Zeigern
Zeiger können verwendet werden, um Variablen anhand ihrer Adresse zu übergeben, sodass der Wert der Wert geändert werden kann. Betrachten Sie beispielsweise Folgendes C Code:
/ * Eine Kopie des Int N kann innerhalb der Funktion geändert werden, ohne den aufrufenden Code zu beeinflussen */ Leere PassByValue(int n) { n = 12; } /* Ein Zeiger m wird stattdessen übergeben. Keine Kopie des Wertes, auf den M verweist, wird erstellt */ wird erstellt */ Leere PassByAddress(int *m) { *m = 14; } int hauptsächlich(Leere) { int x = 3; / * Übergeben Sie eine Kopie von Xs Wert als Argument */ PassByValue(x); // Der Wert wurde in der Funktion geändert, aber X ist immer noch 3 von hier aus / * Übergeben Sie die Adresse von X als Argument *// PassByAddress(&x); // x wurde tatsächlich durch die Funktion geändert und ist jetzt hier gleich 14 Rückkehr 0; }
Dynamische Speicherzuweisung
In einigen Programmen hängt der erforderliche Speicher davon ab, was der Nutzer kann eintreten. In solchen Fällen muss der Programmierer den Speicher dynamisch zuweisen. Dies geschieht durch die Zuweisung von Speicher an der Haufen eher als auf der Stapel, wo Variablen normalerweise gespeichert werden (Variablen können auch in den CPU -Registern gespeichert werden, aber das ist eine andere Angelegenheit). Die dynamische Speicherzuweisung kann nur durch Zeiger erfolgen, und Namen (wie bei gemeinsamen Variablen) können nicht angegeben werden.
Zeiger werden verwendet, um die Adressen von zu speichern und zu verwalten dynamisch zugewiesen Speicherblöcke. Solche Blöcke werden verwendet, um Datenobjekte oder Arrays von Objekten zu speichern. Die meisten strukturierten und objektorientierten Sprachen bieten einen Speicherbereich, der als die genannt wird Haufen oder Kostenloser Laden, aus denen Objekte dynamisch zugewiesen werden.
Der folgende Beispiel -C -Code zeigt, wie Strukturobjekte dynamisch zugewiesen und referenziert werden. Das Standard -C -Bibliothek Bietet die Funktion malloc ()
zum Zuweisen von Speicherblöcken vom Haufen. Es braucht die Größe eines Objekts, um als Parameter zuzuweisen, und gibt einen Zeiger an einen neu zugewiesenen Speicherblock zurück, der zum Speichern des Objekts geeignet ist, oder gibt einen Nullzeiger zurück, wenn die Zuweisung fehlgeschlagen ist.
/ * Teilebestandselement *// Struktur Artikel { int Ich würde; /* Artikelnummer */ verkohlen * Name; /* Teilname */ schweben kosten; /* Kosten */ }; / * Zuordnen und initialisieren Sie ein neues Elementobjekt */ Struktur Artikel * make_item(Const verkohlen *Name) { Struktur Artikel * Artikel; / * Einen Speicherblock für ein neues Element -Objekt zuweisen */ Artikel = Malloc(Größe von(Struktur Artikel)); wenn (Artikel == NULL) Rückkehr NULL; / * Initialisieren Sie die Mitglieder des neuen Elements */ Memset(Artikel, 0, Größe von(Struktur Artikel)); Artikel->Ich würde = -1; Artikel->Name = NULL; Artikel->kosten = 0,0; / * Speichern Sie eine Kopie des Namens im neuen Artikel */ Artikel->Name = Malloc(Strlen(Name) + 1); wenn (Artikel->Name == NULL) { frei(Artikel); Rückkehr NULL; } Strcpy(Artikel->Name, Name); / * Zurück das neu erstellte Element -Objekt *// Rückkehr Artikel; }
Der folgende Code zeigt, wie Speicherobjekte dynamisch behandelt werden, d. H. In den Haufen oder den kostenlosen Speicher zurückgegeben werden. Die Standard -C -Bibliothek bietet die Funktion frei()
Für den Umgang mit einem zuvor zugewiesenen Speicherblock und zur Rückgabe wieder an den Haufen.
/ * Beauftragt ein Elementobjekt */ Leere zerstören_item(Struktur Artikel *Artikel) { / * Nach einem Null -Objektzeiger achten */ wenn (Artikel == NULL) Rückkehr; / * Beauftragt die im Element gespeicherte Namenszeichenfolge */ wenn (Artikel->Name ! = NULL) { frei(Artikel->Name); Artikel->Name = NULL; } / * Beauftragt das Elementobjekt selbst */ frei(Artikel); }
Hardware mit Speichermaked
Bei einigen Computerarchitekturen können Zeiger verwendet werden, um Speicher- oder Speicher-abgebildete Geräte direkt zu manipulieren.
Die Zuweisung von Adressen an Zeiger ist ein unschätzbares Werkzeug beim Programmieren Mikrocontroller. Im Folgenden finden Sie ein einfaches Beispiel, das einen Zeiger vom Typ int und initialisiert zu a erklärt hexadezimal Adresse In diesem Beispiel die Konstante 0x7fff:
int *Hardware_address = (int *)0x7fff;
Mitte der 80er Jahre verwenden die BIOS Der Zugriff auf die Videofunktionen von PCs war langsam. Anwendungen, die displayintensiv waren, typischerweise zum Zugriff CGA Videospeicher direkt, indem Sie das gießen hexadezimal Konstante 0xb8000 auf einen Zeiger auf ein Array von 80 nicht signierten 16-Bit-int-Werten. Jeder Wert bestand aus einem ASCII Code im niedrigen Byte und eine Farbe im hohen Byte. Um den Buchstaben 'A' in Zeile 5, Spalte 2 in Bright White auf Blau zu setzen, schreibt man Code wie folgt:
#define vid ((nicht signiert kurz (*) [80]) 0xb8000) Leere Foo(Leere) { Vid[4][1] = 0x1f00 | 'EIN'; }
Verwenden Sie in Kontrolltabellen
Steuertabellen die zur Kontrolle verwendet werden Programmfluss Normalerweise nutzen Zeiger ausgiebig. Die Zeiger, die normalerweise in einen Tischeintrag eingebettet sind, können beispielsweise verwendet werden, um die Einstiegspunkte an zu halten Unterroutinen Ausführung auf der Grundlage bestimmter Bedingungen, die im gleichen Tabelleneintrag definiert sind. Die Zeiger können jedoch einfach Indizes für andere separate, zugehörige Tabellen sein, die ein Array der tatsächlichen Adressen oder die Adressen selbst (abhängig von den verfügbaren Programmiersprache) umfassen. Sie können auch verwendet werden, um auf frühere Tabelleneinträge (wie in der Schleifenverarbeitung) zu verweisen oder einige Tabelleneinträge zu überspringen (wie in a Schalter oder "frühe" Ausstieg aus einer Schleife). Für diesen letzteren Zweck kann der "Zeiger" einfach die Tabelleneintragsnummer selbst sein und kann durch einfache Arithmetik in eine tatsächliche Adresse umgewandelt werden.
Typed Zeiger und Casting
In vielen Sprachen haben Zeiger die zusätzliche Einschränkung, dass das Objekt, auf das sie hinweisen, eine spezifische Typ. Zum Beispiel kann ein Zeiger deklariert werden, um auf eine zu verweisen ganze Zahl; Die Sprache versucht dann, den Programmierer daran zu hindern, sie auf Objekte zu zeigen, die keine Ganzzahlen sind, wie z. Gleitkommazahleneinige Fehler beseitigen.
Zum Beispiel in c
int *Geld; verkohlen *Taschen;
Geld
wäre ein ganzzahliger Zeiger und Taschen
Wäre ein Zeichenzeiger. Das Folgende würde eine Compiler -Warnung vor "Zuweisung von inkompatiblen Zeigertyp" unterbringen GCC
Taschen = Geld;
Weil Geld
und Taschen
wurden mit verschiedenen Typen deklariert. Um die Compiler -Warnung zu unterdrücken, muss es explizit gemacht werden, dass Sie tatsächlich die Aufgabe durchführen möchten Typecasting es
Taschen = (verkohlen *)Geld;
was sagt, den Ganzzahlzeiger von zu werfen Geld
zu einem Zeichenzeiger und zuweisen an Taschen
.
Ein Entwurf des C -Standards von 2005 erfordert, dass das Gießen eines Zeigers, der von einem Typ auf einen anderen Typ abgeleitet ist, die Ausrichtung der Richtigkeit für beide Typen (6.3.2.3 Zeiger, Abs. 7) beibehalten:[9]
verkohlen *external_buffer = "Abcdef"; int *interne_data; interne_data = (int *)external_buffer; // undefiniertes Verhalten, wenn "der resultierende Zeiger // ist nicht richtig ausgerichtet "
In Sprachen, die Zeigerarithmetik ermöglichen, berücksichtigt die Arithmetik auf Zeigern die Größe des Typs. Zum Beispiel erzeugt das Hinzufügen einer Ganzzahlnummer zu einem Zeiger einen weiteren Zeiger, der auf eine Adresse verweist, die nach der Anzahl der Typen des Typs höher ist. Dies ermöglicht es uns, die Adresse der Elemente eines Arrays eines bestimmten Typs leicht zu berechnen, wie im obigen C -Arrays -Beispiel gezeigt wurde. Wenn ein Zeiger eines Typs auf einen anderen Typ einer anderen Größe gegossen wird, sollte der Programmierer erwarten, dass die Zeigerarithmetik unterschiedlich berechnet wird. In c zum Beispiel, wenn die Geld
Array beginnt bei 0x2000 und Größe (int)
ist 4 Bytes, während Größe (char)
ist dann 1 Byte, dann Geld + 1
wird auf 0x2004 zeigen, aber Taschen + 1
würde auf 0x2001 zeigen. Weitere Casting -Risiken umfassen Datenverlust, wenn "breite" Daten an "enge" Standorte geschrieben werden (z. Taschen [0] = 65537;
), unerwartete Ergebnisse, wenn Bitschiebung Werte und Vergleichsprobleme, insbesondere bei signierten und nicht signierten Werten.
Obwohl es im Allgemeinen unmöglich ist, bei der Kompilierung zu bestimmen, die sicher sind, speichern einige Sprachen. Laufzeitinformationen Dies kann verwendet werden, um zu bestätigen, dass diese gefährlichen Abgüsse zur Laufzeit gültig sind. Andere Sprachen akzeptieren lediglich eine konservative Annäherung an sichere Abgüsse oder gar keine.
Wert von Zeigern
In C und C ++ ist das Ergebnis des Vergleichs zwischen Zeigern undefiniert. In diesen Sprachen und LlvmDie Regel wird so interpretiert, dass "nur weil zwei Zeiger auf die gleiche Adresse hinweisen, nicht gleich sind und austauschbar verwendet werden können", der Unterschied zwischen den Zeigern, die als ihre bezeichnet werden Herkunft.[10] Obwohl wir auf einen Ganzzahl -Typ wie z. uintptr_t
Bietet Vergleich, die Besetzung selbst ist implementierungsdefiniert. Darüber hinaus wird die weitere Konvertierung von Bytes und Arithmetikern Optimierer abwerfen, die versuchen, die Verwendung von Zeigern zu verfolgen, wobei ein Problem immer noch in der akademischen Forschung aufgeklärt wird.[11]
Zeiger sicherer machen
Als Zeiger kann ein Programm versuchen, auf ein Objekt zuzugreifen, das möglicherweise nicht definiert wird, können Zeiger der Ursprung einer Vielzahl von sein Programmierfehler. Die Nützlichkeit von Zeigern ist jedoch so groß, dass es schwierig sein kann, Programmieraufgaben ohne sie auszuführen. Infolgedessen haben viele Sprachen Konstrukte erstellt, die einige der nützlichen Merkmale von Zeigern ohne einige ihrer bereitstellen sollen Tücken, auch manchmal bezeichnet als Zeigergefahren. In diesem Zusammenhang werden Zeiger, die den Speicher direkt ansprechen (wie in diesem Artikel verwendet), als bezeichnet Rohzeigersdagegen mit Smart Zeiger oder andere Varianten.
Ein Hauptproblem bei Zeigern ist, dass sie, solange sie direkt als Zahl manipuliert werden können, auf nicht verwendete Adressen oder auf Daten verweisen, die für andere Zwecke verwendet werden. Viele Sprachen, einschließlich der meisten Funktionale Programmiersprachen und neuer Imperative Sprachen wie JavaErsetzen Sie Zeiger durch eine undurchsichtigere Referenztyp, die typischerweise als einfach als einfach bezeichnet wird Hinweis, mit dem nur verwendet werden kann, um Objekte zu beziehen und nicht als Zahlen manipuliert zu werden, wodurch diese Art von Fehler verhindert wird. Die Array -Indexierung wird als Sonderfall behandelt.
Ein Zeiger, der keine Adresse zugewiesen wird, heißt a Wild Zeiger. Jeder Versuch, solche nicht initialisierten Zeiger zu verwenden, kann ein unerwartetes Verhalten verursachen, entweder weil der Anfangswert keine gültige Adresse ist oder weil es andere Teile des Programms beschädigen kann. Das Ergebnis ist oft a Segmentierungsfehler, Speicherverletzung oder Wildzweig (wenn als Funktionszeiger oder Zweigadresse verwendet).
In Systemen mit explizitem Speicherzuweisung ist es möglich, a zu erstellen baumelnder Zeiger Indem er den Speicherbereich umgeht, zeigt er in. Diese Art von Zeiger ist gefährlich und subtil, da eine verationale Speicherregion möglicherweise die gleichen Daten enthalten kann, die er vor dem Dealloced, aber dann durch den nicht verwandten Code, der dem früheren Code unbekannt ist, neu zugewandt und überschrieben wurde. Sprachen mit Müllsammlung Verhindern Sie diese Art von Fehler, da die Depandierung automatisch durchgeführt wird, wenn keine Referenzen im Bereich mehr Referenzen enthalten.
Einige Sprachen wie C ++, Unterstützung Smart Zeiger, die eine einfache Form von verwenden Referenzzählung Um die Zuordnung des dynamischen Speichers zu verfolgen und als Referenz zu wirken. In Ermangelung von Referenzzyklen, in denen sich ein Objekt indirekt durch eine Abfolge von intelligenten Zeigern auf sich selbst bezieht, beseitigen diese die Möglichkeit, Zeiger und Speicherlecks zu baumeln. Delphi Saiten unterstützen Referenzzählen nativ.
Das Rost -Programmiersprache führt a ein Ausleihen Sie Checker aus, Zeigerlebensdauerund eine Optimierung basierend Optionale Typen zum Nullzeiger Zeigerfehler zu beseitigen, ohne auf zurückzugreifen auf Müllsammlung.
Besondere Arten von Zeigern
Arten, die durch Wert definiert sind
Null Zeiger
A Null Zeiger hat einen Wert, der für die Angabe des Zeigers reserviert ist, dass sich der Zeiger nicht auf ein gültiges Objekt bezieht. Nullzeiger werden routinemäßig zur Darstellung von Bedingungen wie dem Ende von a verwendet aufführen von unbekannter Länge oder dem Versäumnis, etwas Aktion auszuführen; Diese Verwendung von Nullzeiger kann verglichen werden Nullbare Typen und zum Nichts Wert in an Optionstyp.
Baumelnder Zeiger
A baumelnder Zeiger ist ein Zeiger, der nicht auf ein gültiges Objekt verweist und folglich einen Programm zum Absturz bringen oder sich seltsam verhalten kann. In dem Pascal oder C Programmiersprachen, Zeiger, die nicht spezifisch initialisiert werden, können auf unvorhersehbare Adressen im Speicher hinweisen.
Der folgende Beispielcode zeigt einen baumelnden Zeiger:
int Func(Leere) { verkohlen *P1 = Malloc(Größe von(verkohlen)); / * (undefinierter) Wert eines Ortes auf dem Haufen */ verkohlen *p2; / * baumeln (nicht initialisiert) Zeiger */ *P1 = 'a'; /* Dies ist in Ordnung, vorausgesetzt, Malloc () hat NULL nicht zurückgegeben. */ *p2 = 'b'; / * Dies ruft ein undefiniertes Verhalten auf */ }
Hier, p2
kann auf überall im Speicher hinweisen, sodass die Aufgabe durchführen *p2 = 'b';
kann einen unbekannten Speicherbereich oder einen unbekannten Speicherbereich durchführen oder a auslösen Segmentierungsfehler.
Wildzweig
Wo ein Zeiger als Adresse des Einstiegspunkts zu einem Programm oder Start von a verwendet wird Funktion, die nichts zurückgibt und wird auch entweder nicht initialisiert oder beschädigt, wenn ein Anruf oder springen wird dennoch an diese Adresse gemacht, a "Wildzweig"soll aufgetreten sein. Mit anderen Worten, ein wilder Zweig ist ein wild (baumelner) Funktionzeiger.
Die Konsequenzen sind normalerweise unvorhersehbar und der Fehler kann sich auf verschiedene Weise auf verschiedene Weise darstellen, je nachdem, ob der Zeiger eine "gültige" Adresse ist und ob es (zufällig) eine gültige Anweisung (Opcode) an dieser Adresse gibt oder nicht. Die Erkennung eines wilden Zweigs kann eine der schwierigsten und frustrierendsten Debugging -Übungen vorlegen, da ein Großteil der Beweise bereits zuvor oder durch Ausführung einer oder mehrerer unangemessener Anweisungen am Zweigstandort zerstört wurde. Falls vorhanden, eine Anweisungssatz Simulator Kann normalerweise nicht nur einen wilden Zweig erkennen, bevor er wirksam wird, sondern auch eine vollständige oder teilweise Spur seiner Geschichte liefern.
Arten durch Struktur definiert
Autorelatives Zeiger
Ein Autorelatives Zeiger ist ein Zeiger, dessen Wert als Offset von der Adresse des Zeigers selbst interpretiert wird; Wenn eine Datenstruktur ein autorelatives Zeigerelement hat, das auf einen Teil der Datenstruktur selbst hinweist, kann die Datenstruktur in Speicher verschoben werden, ohne den Wert des automatischen relativen Zeigers zu aktualisieren.[12]
Das zitierte Patent verwendet auch den Begriff Selbstbezogener Zeiger das Gleiche zu bedeuten. Die Bedeutung dieses Begriffs wurde jedoch auf andere Weise verwendet:
- einen Versatz von der Adresse einer Struktur und nicht aus der Adresse des Zeigers selbst zu bedeuten;
- um einen Zeiger zu bedeuten, der eine eigene Adresse enthält, die nützlich sein kann, um in einem beliebigen Bereich des Speichers eine Sammlung von Datenstrukturen zu rekonstruieren, die auf einander hinweisen.[13]
Basierter Zeiger
A basierter Zeiger ist ein Zeiger, dessen Wert ein Versatz aus dem Wert eines anderen Zeigers ist. Dies kann verwendet werden, um Datenblöcke zu speichern und zu laden, wodurch der Basizzeiger die Adresse des Beginns des Blocks zugewiesen wird.[14]
Arten, die durch Verwendung oder Datentyp definiert sind
Multiple Indirektion
In einigen Sprachen kann ein Zeiger auf einen anderen Zeiger verweisen, wobei mehrere Dereference -Operationen zum ursprünglichen Wert erreicht werden müssen. Während jede Indirektionsstufe die Leistungskosten erhöhen kann, ist es manchmal erforderlich, um ein korrektes Verhalten für Komplexe zu erhalten Datenstrukturen. Zum Beispiel ist es in C typisch, a zu definieren verlinkte Liste In Bezug auf ein Element, das einen Zeiger auf das nächste Element der Liste enthält:
Struktur Element { Struktur Element *nächste; int Wert; }; Struktur Element *Kopf = NULL;
Diese Implementierung verwendet einen Zeiger auf das erste Element in der Liste als Ersatz für die gesamte Liste. Wenn zum Beginn der Liste ein neuer Wert hinzugefügt wird, Kopf
muss geändert werden, um auf das neue Element hinzuweisen. Da C-Argumente immer von Wert übergeben werden, ermöglicht die Verwendung der doppelten Indirektion die korrekte Implementierung der Einfügung und hat den wünschenswerten Nebeneffekt, Spezialfallcode zu beseitigen, um mit Einfügen an der Liste der Liste zu handeln:
// Fügen Sie das Elementelement am ersten ein // Ort, an dem alle früheren Elemente einen geringeren oder gleichen Wert haben. Leere Einfügung(Struktur Element **Kopf, Struktur Element *Artikel) { Struktur Element **p; // p zeigt auf einen Zeiger auf ein Element zum (p = Kopf; *p ! = NULL; p = &(*p)->nächste) { wenn (Artikel->Wert <= (*p)->Wert) Unterbrechung; } Artikel->nächste = *p; *p = Artikel; } // Anrufer macht dies: Einfügung(&Kopf, Artikel);
In diesem Fall, wenn der Wert von Artikel
ist weniger als das von Kopf
, der Anrufer Kopf
wird ordnungsgemäß auf die Adresse des neuen Elements aktualisiert.
Ein grundlegendes Beispiel ist in der argv Argument an die Hauptfunktion in C (und C ++), was im Prototyp als angegeben ist char ** argv
- Das liegt an der Variablen argv
selbst ist ein Zeiger auf eine Reihe von Saiten (eine Reihe von Arrays), also *argv
ist ein Zeiger auf die 0. Zeichenfolge (durch Konvention des Namens des Programms) und ** Argv
ist das 0. Zeichen der 0. Zeichenfolge.
Funktionszeiger
In einigen Sprachen kann ein Zeiger ausführbarer Code verweisen, d. H. Er kann auf eine Funktion, Methode oder Prozedur verweisen. EIN Funktionszeiger speichert die Adresse einer aufgerufenen Funktion. Während diese Einrichtung verwendet werden kann, um Funktionen dynamisch aufzurufen, ist sie oft eine Lieblingstechnik des Virus und anderer bösartiger Softwareautoren.
int Summe(int N1, int N2) { // Funktion mit zwei Ganzzahlparametern, die einen Ganzzahlwert zurückgeben Rückkehr N1 + N2; } int hauptsächlich(Leere) { int a, b, x, y; int (*FP) (int, int); // Funktionszeiger, der auf eine Funktion wie Summe verweisen kann FP = &Summe; // FP zeigt jetzt auf Funktionsumme x = (*FP) (a, b); // Aufrufe Funktionssumme mit den Argumenten A und B y = Summe(a, b); // Aufrufe Funktionssumme mit den Argumenten A und B }
Rückzeiger
Doppelt verlinkte Listen oder Baumstrukturen, ein Rückenzeiger, der auf ein Element "zurückgibt" auf das Element, das sich auf das aktuelle Element bezieht. Diese sind nützlich für die Navigation und Manipulation, auf Kosten des größeren Speichergebrauchs.
Simulation unter Verwendung eines Array -Index
Es ist möglich, Zeigerverhalten mit einem Index zu einem (normalerweise eindimensionalen) Array zu simulieren.
Vor allem für Sprachen, die Zeiger nicht explizit aber explizit unterstützen tun Unterstützen Sie Arrays, die Array kann an und verarbeitet werden, als wäre es der gesamte Speicherbereich (im Rahmen des jeweiligen Arrays), und jeder Index kann als gleichwertig zu einem betrachtet werden Allzweckregister in der Montagesprache (die auf die einzelnen Bytes hinweist, der jedoch den tatsächlichen Wert relativ zum Beginn des Arrays ist, nicht zur absoluten Adresse im Speicher). Angenommen, das Array ist beispielsweise eine angrenzende 16 Megabyte Charakter Datenstruktur, einzelne Bytes (oder a Saite von zusammenhängenden Bytes innerhalb des Arrays) kann direkt mit dem Namen des Arrays mit einem 31 -Bit -Bit unsigniert angesprochen und manipuliert werden ganze Zahl als simulierter Zeiger (das ist dem ziemlich ähnlich wie C Arrays Beispiel oben gezeigt). Die Zeigerarithmetik kann durch Hinzufügen oder Subtrahieren des Index mit minimalem zusätzlichen Overhead im Vergleich zu echter Zeigerarithmetik simuliert werden.
Es ist sogar theoretisch möglich, und die obige Technik zusammen mit einem geeigneten Anweisungssatz Simulator simulieren irgendein Maschinensprache oder das Zwischenprodukt (Byte -Code) von irgendein Prozessor/Sprache in einer anderen Sprache, die Zeiger überhaupt nicht unterstützt (zum Beispiel Java / JavaScript). Um dies zu erreichen, die binär Der Code kann zunächst in zusammenhängende Bytes des Arrays geladen werden, damit der Simulator "lesen", interpretiert und vollständig innerhalb des Speichers desselben Arrays enthält. Gegebenenfalls, um vollständig zu vermeiden Pufferüberlauf Probleme, Grenzenprüfung kann normalerweise für den Compiler (oder falls nicht, handkodiert im Simulator) aktiviert werden.
Unterstützung in verschiedenen Programmiersprachen
Ada
Ada ist eine stark typisierte Sprache, in der alle Zeiger getippt werden und nur sichere Konvertierungen vom Typ sichtbar sind. Alle Zeiger werden standardmäßig initialisiert Null
, und jeder Versuch, über a auf Daten zugreifen zu können Null
Zeiger verursacht An Ausnahme aufgezogen werden. Zeiger in ADA werden genannt Zugangstypen. ADA 83 erlaubte keine Arithmetik für Zugangstypen (obwohl viele Compiler-Anbieter als nicht standardmäßige Funktion vorgesehen sind), aber ADA 95 unterstützt die „sichere“ Arithmetik für Zugangstypen über das Paket System.Storage_Elements
.
BASIC
Mehrere alte Versionen von BASIC Für die Windows -Plattform wurde Strptr () unterstützt, die Adresse einer Zeichenfolge zurückzugeben, und für VAGTR (), die Adresse einer Variablen zurückzugeben. Visual Basic 5 hatte auch Unterstützung für OBJPTR (), die Adresse einer Objektschnittstelle zurückzugeben, und für einen Adresse des Operators, um die Adresse einer Funktion zurückzugeben. Die Arten aller dieser sind Ganzzahlen, aber ihre Werte entsprechen denen, die von Zeigertypen gehalten werden.
Neuere Dialekte von BASIC, wie zum Beispiel Freilasisch oder Blitzmaxhaben jedoch erschöpfende Zeigerimplementierungen. In freier Bereitschaft, Arithmetik auf IRGENDEIN
Zeiger (entspricht C's Leere*
) werden behandelt, als ob das IRGENDEIN
Zeiger war eine Bytebreite. IRGENDEIN
Zeiger können nicht wie in C. auch in Casting zwischen den IRGENDEIN
Und die Zeiger eines anderen Typs werden keine Warnungen erzeugen.
schwach wie ganze Zahl f = 257 schwach wie irgendein ptr g = @f schwach wie ganze Zahl ptr i = g behaupten(*i = 257) behaupten( (g + 4) = (@f + 1) )
C und C ++
Im C und C ++ Zeiger sind Variablen, die Adressen speichern und sein können Null. Jeder Zeiger hat einen Typ, auf den es zeigt, aber man kann frei zwischen Zeigertypen (aber nicht zwischen einem Funktionszeiger und einem Objektzeiger) gegossen werden. Ein spezieller Zeigertyp, der als „voidem Zeiger“ bezeichnet wird, ermöglicht es, auf ein (nicht funktionsfähiges) Objekt zu zeigen, wird jedoch durch die Tatsache begrenzt, dass er nicht direkt Derferenziert werden kann (er muss gegossen werden). Die Adresse selbst kann oft direkt manipuliert werden, indem ein Zeiger zu und von einer integralen Art von ausreichender Größe geworfen wird, obwohl die Ergebnisse implementiert sind und tatsächlich ein definiertes Verhalten verursachen können. Während frühere C -Standards keinen integralen Typ hatten, der garantiert groß genug war, hatte C99 Gibt die an uintptr_t
Typedef Name definiert in
, aber eine Implementierung muss sie nicht bereitstellen.
C ++ Unterstützt C -Zeiger und C -Typen. Es unterstützt auch eine neue Gruppe typischer Anbieter, um einige unbeabsichtigte gefährliche Casts zum Zeitpunkt der Kompilierung zu fangen. Seit C ++ 11, das C ++ Standardbibliothek stellt ebenfalls zur Verfügung Smart Zeiger (Unique_ptr
, Shared_ptr
und WACK_PTR
), die in einigen Situationen als sicherere Alternative zu primitiven C -Zeigern verwendet werden können. C ++ unterstützt auch eine andere Referenzform, die sich von einem Zeiger unterscheidet, einfach a Hinweis oder Referenztyp.
ZeigerarithmetikDas heißt, die Fähigkeit, die Zieladresse eines Zeigers mit arithmetischen Operationen (sowie Größenvergleiche) zu ändern undefiniertes Verhalten. Das Hinzufügen oder Subtrahieren von einem Zeiger bewegt ihn durch ein Vielfaches der Größe seiner Größe Datentyp. Beispielsweise erhöht das Hinzufügen von 1 zu einem Zeiger zu 4-Byte-Ganzzahl-Werten die spitzen Zeiger-zu-Byte-Adress um 4. Dies hat den Einfluss, den Zeiger in das nächste Element in einer zusammenhängenden Array von Ganzzahlen zu erhöhen-was ist-das ist-das ist, was ist-was ist-was ist-was ist-was ist-was ist Oft das beabsichtigte Ergebnis. Zeigerarithmetik kann nicht durchgeführt werden Leere
Zeiger, weil die Hohlraumtyp hat keine Größe, und daher kann die spitze Adresse nicht hinzugefügt werden, obwohl obwohl GCC und andere Compiler werden Byte -Arithmetik auf Leere*
Als nicht standardmäßige Erweiterung behandeln Sie es so, als ob es wäre char *
.
Die Zeigerarithmetik bietet dem Programmierer eine einzige Möglichkeit, mit verschiedenen Typen umzugehen: Hinzufügen und Subtrahieren der Anzahl der erforderlichen Elemente anstelle des tatsächlichen Offsets in Bytes. (Zeigerarithmetik mit char *
Zeiger verwenden Byte -Offsets, weil Größe (char)
ist 1 per Definition.) Insbesondere die C -Definition erklärt ausdrücklich, dass die Syntax ein]
, was das ist n
-Die Element des Arrays a
, ist äquivalent zu *(a + n)
, was der Inhalt des Elements ist, das nachgewiesen wird a + n
. Dies impliziert das n / A]
ist äquivalent zu ein]
und man kann schreiben, z. B., A [3]
oder 3 [a]
Ebenso gut, um auf das vierte Element eines Arrays zuzugreifen a
.
Während leistungsstark kann Zeigerarithmetik eine Quelle von sein Computerfehler. Es neigt dazu, den Neuling zu verwirren Programmierer, um sie in verschiedene Kontexte zu zwingen: Ein Ausdruck kann ein gewöhnlicher arithmetischer oder ein Zeiger arithmetischer sein, und manchmal ist es einfach, einen für den anderen zu verwechseln. Als Reaktion darauf viele moderne Computersprachen auf hoher Ebene (zum Beispiel Java) Erlauben Sie keinen direkten Zugriff auf den Speicher mithilfe von Adressen. Auch der sichere C -Dialekt Zyklon befasst sich mit vielen Problemen mit Zeigern. Sehen C Programmiersprache Für weitere Diskussionen.
Das Leere
Zeiger, oder Leere*
, wird in ANSI C und C ++ als generischer Zeigertyp unterstützt. Ein Zeiger auf Leere
Kann die Adresse eines Objekts (nicht Funktion) speichern und in C implizit in einen anderen Objektzeigertyp bei der Zuordnung konvertiert werden, muss jedoch ausdrücklich gegossen werden, wenn sie die Eferenzusammenhänge haben.K & r C verwendet verkohlen*
Für den Zweck „Typnostischer Zeiger“ (vor Ansi C).
int x = 4; Leere* P1 = &x; int* p2 = P1; // void* implizit in int*: gültig c, aber nicht c ++ konvertiert int a = *p2; int b = *(int*)P1; // Wenn die Inline -Inline -Einführung in der Einstellung ist, gibt es keine implizite Konvertierung
C ++ erlaubt nicht die implizite Umwandlung von Leere*
an andere Zeigertypen, auch in Aufgaben. Dies war eine Entwurfsentscheidung, um unachtsame und sogar unbeabsichtigte Abgüsse zu vermeiden, obwohl die meisten Compiler nur Warnungen ausgaben, keine Fehler, wenn sie auf andere Abgüsse stoßen.
int x = 4; Leere* P1 = &x; int* p2 = P1; // Dies scheitert in C ++: Es gibt keine implizite Konvertierung von void* int* p3 = (int*)P1; // C-Cast int* p4 = static_cast<int*>(P1); // C ++ Guss
In C ++ gibt es keine Leere&
(Verweis auf Leere) auf Komplement Leere*
(Zeiger auf void), da sich Referenzen wie Aliase auf die Variablen verhalten, auf die sie hinweisen, und es kann niemals eine Variable geben, deren Typ ist Leere
.
Zeigerdeklarationssyntaxübersicht
Diese Zeigerdeklarationen decken die meisten Varianten von Zeigerdeklarationen ab. Natürlich ist es möglich, dreifache Zeiger zu haben, aber die Hauptprinzipien hinter einem dreifachen Zeiger bestehen bereits in einem Doppelzeiger.
verkohlen CFF [5][5]; / * Array von Arrays von Zeichen *// verkohlen *CFP [5]; / * Array von Zeigern auf Zeichen */ verkohlen **CPP; / * Zeiger auf Zeiger auf char ("Doppelzeiger") *// verkohlen (*CPF) [5]; / * Zeiger auf Array (s) von Zeichen */ verkohlen *CPF(); / * Funktion, die einen Zeiger auf Zeichen zurückgibt (s) */ verkohlen (*CFP) (); / * Zeiger auf eine Funktion, die ein Zeichen zurückgibt */ verkohlen (*CFPF()) [5]; / * Funktion, die Zeiger auf eine Reihe von Zeichen zurückgibt */// verkohlen (*CPFF[5]) (); / * eine Reihe von Zeigern auf Funktionen, die ein Zeichen zurückgeben */
Die () und [] haben eine höhere Priorität als *.[15]
C#
In dem C# Programmiersprache, Zeiger werden nur unter bestimmten Bedingungen unterstützt: Jeder Codeblock, einschließlich Zeiger, muss mit dem gekennzeichnet sein unsicher
Stichwort. Solche Blöcke erfordern normalerweise höhere Sicherheitsberechtigungen, um auszuführen. Die Syntax ist im Wesentlichen die gleiche wie in C ++, und die angegebene Adresse kann entweder sein gelang es oder nicht verwaltet Erinnerung. Zeiger auf den verwalteten Speicher (jeder Zeiger auf ein verwaltetes Objekt) muss jedoch mit dem deklariert werden Fest
Schlüsselwort, das das verhindert Müllsammler Aus dem Verschieben des spitzen Objekts als Teil des Speichermanagements, während sich der Zeiger im Bereich befindet und so die Zeigeradresse gültig hält.
Eine Ausnahme davon ist die Verwendung der Verwendung der Intptr
Struktur, die ein sicheres, der gleichwertig ist, zu int*
und benötigt keinen unsicheren Code. Dieser Typ wird häufig zurückgegeben, wenn Methoden von der verwendet werden System.Runtime.Interopervices
, zum Beispiel:
// Holen Sie sich 16 Bytes Speicher aus dem unermanagierten Speicher des Prozesses Intptr Zeiger = System.Laufzeit.Interopices.Marschall.Allochglobal(16); // etwas mit dem zugewiesenen Speicher machen // den zugewiesenen Speicher befreien System.Laufzeit.Interopices.Marschall.Freehglobal(Zeiger);
Das .NET Framework beinhaltet viele Klassen und Methoden in der System
und System.Runtime.Interopervices
Namespaces (wie die Marschall
Klasse), die .NET -Typen konvertieren (zum Beispiel, System.String
) zu und von vielen nicht verwaltet Typen und Zeiger (zum Beispiel, Lpwstr
oder Leere*
) um die Kommunikation mit zuzulassen nicht verwalteter Code. Die meisten dieser Methoden haben die gleichen Sicherheitsberechtigungsanforderungen wie nicht verwaltetes Code, da sie die beliebigen Stellen im Speicher beeinflussen können.
Cobol
Das Cobol Die Programmiersprache unterstützt Hinweise auf Variablen. Primitive oder Gruppen- (Datensatz-) Datenobjekte, die innerhalb der deklarierten Verknüpfungsabschnitt
eines Programms sind von Natur aus Zeigerbasiert, wobei der einzige im Programm zugewiesene Speicher Platz für die Adresse des Datenelements ist (normalerweise ein einzelnes Speicherwort). Im Programmquellcode werden diese Datenelemente wie jeder andere verwendet Arbeitsspeicher
Variable, aber ihr Inhalt wird implizit indirekt durch ihre zugreifen Verknüpfung
Zeiger.
Der Speicherplatz für jedes Datenobjekt ist in der Regel dynamisch zugewiesen Verwenden von extern ANRUF
Aussagen oder über eingebettete erweiterte Sprachkonstrukte wie z. Führungskräfte CICS
oder Exec SQL
Aussagen.
Erweiterte Versionen von COBOL liefern auch Zeigervariablen, die mit deklariert sind VERWENDUNGSZWECK
IST
ZEIGER
Klauseln. Die Werte solcher Zeigervariablen werden festgelegt und modifiziert unter Verwendung EINSTELLEN
und EINSTELLEN
DIE ANSCHRIFT
Aussagen.
Einige erweiterte Versionen von COBOL bieten auch Vorgangszeiger
Variablen, die die speichern können Adressen des ausführbaren Code.
Pl/i
Das Pl/i Die Sprache unterstützt die Hinweise auf alle Datentypen (einschließlich Zeiger auf Strukturen). Rekursion, Multitasking, Stringhandhabung und umfangreiche Einbauten Funktionen. PL/Ich war im Vergleich zu den Programmiersprachen seiner Zeit ein ziemlicher Sprung nach vorne. PL/I -Zeiger sind nicht typisch, und daher ist kein Guss für Zeiger Dereferenzierung oder Zuordnung erforderlich. Die Deklarationssyntax für einen Zeiger ist Xxxzeiger deklarieren;
, was einen Zeiger namens "xxx" erklärt. Zeiger werden mit verwendet BASIEREND
Variablen. Eine basierte Variable kann mit einem Standard -Locator deklariert werden (Declare XXX basiert (PPP);
oder ohne (Deklariere xxx basiert;
), wobei xxx eine basierte Variable ist, die eine Elementvariable, eine Struktur oder ein Array sein kann, und PPP ist der Standardzeiger). Eine solche Variable kann ohne explizite Zeigerreferenz Adresse sein (xxx = 1;
oder kann mit einem expliziten Verweis auf den Standard -Locator (PPP) oder auf einen anderen Zeiger (PPP) angegangen werden (qqq-> xxx = 1;
).
Die Zeigerarithmetik ist nicht Teil des PL/I -Standards, aber viele Compiler erlauben Ausdrücke der Form PTR = PTR ± Expression
. IBM PL/Ich habe auch die gebaute Funktion Ptradd
die Arithmetik ausführen. Die Zeigerarithmetik wird immer in Bytes durchgeführt.
IBM Unternehmen PL/I -Compiler haben eine neue Form des typisierten Zeigers namens a HANDHABEN
.
D
Das D Programmiersprache ist ein Derivat von C und C ++, das C -Zeiger und C -Typen vollständig unterstützt.
Eiffel
Das Eiffeltellorientierte Sprache verwendet Wert- und Referenzsemantik ohne Zeigerarithmetik. Trotzdem werden Zeigerkurse zur Verfügung gestellt. Sie bieten Zeiger-Arithmetik, Typecasting, explizites Speichermanagement, Vernetzung mit Nicht-Eiffel-Software und anderen Funktionen an.
Forran
Fortran-90 führte eine stark typisierte Zeigerfunktion ein. Forran -Zeiger enthalten mehr als nur eine einfache Speicheradresse. Sie verringern auch die unteren und oberen Grenzen der Array -Abmessungen, Schritte (z. B. um beliebige Arrayabschnitte) und andere Metadaten. Ein Assoziationsbetreiber, =>
wird verwendet, um a zu assoziieren ZEIGER
zu einer Variablen mit a ZIEL
Attribut. Der Fortran-90 Zuweisen
Die Anweisung kann auch verwendet werden, um einen Zeiger mit einem Speicherblock zu verbinden. Beispielsweise kann der folgende Code verwendet werden, um eine verknüpfte Listenstruktur zu definieren und zu erstellen:
Typ real_list_t real :: Beispieldaten(100) Typ (real_list_t), Zeiger :: nächste => Null () Endtyp Typ (real_list_t), Ziel :: my_real_list Typ (real_list_t), Zeiger :: real_list_temp real_list_temp => my_real_list tun lesen (1,Iostat=ioerr) real_list_temp%Beispieldaten wenn (ioerr /= 0) Ausfahrt zuweisen (real_list_temp%nächste) real_list_temp => real_list_temp%nächste Ende tun
Fortran-2003 fügt Unterstützung für Verfahrenszeiger hinzu. Auch als Teil der C Interoperabilität Feature unterstützt die Intran-2003 intrinsischen Funktionen für die Umwandlung von Zeigern im C-Stil in FORTRAN-Zeiger und Rücken.
gehen
gehen hat Zeiger. Die Deklarationssyntax entspricht der von C, aber umgekehrt und endet mit dem Typ. Im Gegensatz zu C hat Go Müllsammlung und macht Zeigerarithmetik. Referenztypen wie in C ++ existieren nicht. Einige eingebaute Typen, wie Karten und Kanäle, werden beschpen (d. H. Innen sind sie Zeiger auf veränderliche Strukturen) und werden mit dem initialisiert machen
Funktion. In einem Ansatz zur einheitlichen Syntax zwischen Zeigern und Nichtbekämpfen, der Pfeil (->
) Der Bediener wurde fallen gelassen: Der Punktoperator auf einem Zeiger bezieht sich auf das Feld oder die Methode des Dereferened -Objekts. Dies funktioniert jedoch nur mit 1 Indirektionsstufe.
Java
Es gibt keine explizite Darstellung von Zeigern in Java. Stattdessen wie komplexere Datenstrukturen wie Objekte und Arrays werden verwendet Verweise. Die Sprache liefert keine expliziten Zeigermanipulationsoperatoren. Es ist jedoch immer noch möglich, dass Code versucht, eine Nullreferenz (Nullzeiger) zu Dereference, was zu einer Laufzeit führt Ausnahme geworfen werden. Der von nicht referenzierten Speicherobjekten besetzt wird automatisch durch wiederhergestellt Müllsammlung zur Laufzeit.[16]
Modula-2
Zeiger werden sehr wie in Pascal umgesetzt, wie es sind Var
Parameter in Prozeduraufrufen. Modula-2 ist noch stärker tippt als Pascal, mit weniger Möglichkeiten, dem Typsystem zu entkommen. Einige der Varianten von Modula-2 (wie z. Modula-3) Müllsammlung einbeziehen.
Oberon
Ähnlich wie bei Modula-2 sind Zeiger verfügbar. Es gibt noch weniger Möglichkeiten, sich dem Typsystem zu entziehen und so Oberon und seine Varianten sind in Bezug auf Zeiger noch sicherer als Modula-2 oder seine Varianten. Wie mit Modula-3, Müllsammlung ist Teil der Sprachspezifikation.
Pascal
Im Gegensatz zu vielen Sprachen mit Zeigern, Standard ISO Pascal Ermöglicht Zeiger nur, dynamisch erstellte Variablen zu verweisen, die anonym sind, und erlaubt ihnen nicht, statische oder lokale Variablen zu referenzieren.[17] Es hat keine Zeigerarithmetik. Die Zeiger müssen auch einen zugeordneten Typ haben und ein Zeiger auf einen Typ ist nicht mit einem Zeiger auf einen anderen Typ kompatibel (z. B. ein Zeiger auf ein Zeichen ist nicht mit einem Zeiger auf eine Ganzzahl kompatibel). Dies hilft dabei Pl/i oder C. Es entfernt auch einige Risiken durch Zeiger baumeln, aber die Fähigkeit, referenzierter Raum dynamisch loszulassen, indem sie die verwenden entsorgen
Standardverfahren (das den gleichen Effekt hat wie das frei
Bibliotheksfunktion gefunden in C) bedeutet, dass das Risiko, Zeiger zu baumeln, nicht vollständig beseitigt wurde.[18]
In einigen kommerziellen und Open -Source -Pascal- (oder Derivaten) Compiler -Implementierungen - wie Kostenloser Pascal,[19] Turbo Pascal oder der Objekt Pascal in Embarcadero Delphi- Ein Zeiger darf Standard -statische oder lokale Variablen referenzieren und kann von einem Zeigertyp auf einen anderen gegossen werden. Darüber hinaus ist die Zeigerarithmetik uneingeschränkt: Hinzufügen oder Subtrahieren von einem Zeiger bewegt es durch diese Anzahl von Bytes in beide Richtungen, aber die Verwendung der Inc.
oder Dez
Standardverfahren damit bewegt den Zeiger nach der Größe der Datentyp es ist erklärt deuten auf. Unter dem Namen wird auch ein untypter Zeiger bereitgestellt Zeiger
, was mit anderen Zeigertypen kompatibel ist.
Perl
Das Perl Programmiersprache Unterstützt Zeiger, obwohl selten in Form der Pack- und Auspackfunktionen verwendet. Diese sind nur für einfache Interaktionen mit kompilierten Betriebssystembibliotheken gedacht. In allen anderen Fällen verwendet Perl Verweise, die getippt werden und keine Form der Zeigerarithmetik zulassen. Sie werden verwendet, um komplexe Datenstrukturen zu konstruieren.[20]
Siehe auch
Verweise
- ^ Donald Knuth (1974). "Strukturiertes Programmieren mit Go to Aussagen" (PDF). Computerumfragen. 6 (5): 261–301. Citeseerx 10.1.1.103.6084. doi:10.1145/356635.356640. S2CID 207630080. Archiviert von das Original (PDF) am 24. August 2009.
- ^ Reilly, Edwin D. (2003). Meilensteine in Informatik und Informationstechnologie. Greenwood Publishing Group. p.204. ISBN 9781573565219. Abgerufen 2018-04-13.
Harold Lawson Zeiger.
- ^ "IEEE Computer Society Awards List". Awards.computer.org. Archiviert von das Original Am 2011-03-22. Abgerufen 2018-04-13.
- ^ ISO/IEC 9899, Klausel 6.7.5.1, Absatz 1.
- ^ ISO/IEC 9899, Klausel 6.7.8, Absatz 10.
- ^ ISO/IEC 9899, Klausel 7.17, Absatz 3: Null ... was zu einer implementierungsdefinierten Nullzeigerkonstante erweitert ...
- ^ ISO/IEC 9899, Klausel 6.5.3.2, Absatz 4, Fußnote 87: Wenn dem Zeiger ein ungültiger Wert zugeordnet wurde, ist das Verhalten des Unary * -Preibers undefiniert. Unter den ungültigen Werten für die Dereration eines Zeigers des Unary * -Preibers sind ein Nullzeiger ...
- ^ a b Plauger, P J.; Brodie, Jim (1992). ANSI- und ISO -Standard -C -Programmiererreferenz. Redmond, WA: Microsoft Press. pp.108, 51. ISBN 978-1-55615-359-4.
Ein Array -Typ enthält keine zusätzlichen Löcher, da alle anderen Typen festgepackt sind, wenn sie in Arrays komponiert werden [auf Seite 51]
- ^ WG14 N1124, C - Genehmigte Standards: ISO/IEC 9899 - Programmiersprachen - c, 2005-05-06.
- ^ Jung, Ralf. "Zeiger sind kompliziert ii oder: Wir brauchen bessere Sprachspezifikationen".
- ^ Jung, Ralf. "Zeiger sind kompliziert oder: Was ist in einem Byte?".
- ^ US -Patent 6625718, Steiner, Robert C. (Broomfield, CO), "Zeiger, die relativ zu ihren eigenen gegenwärtigen Standorten sind", erteilte 2003-09-23, der Avaya Technology Corp. (Basking Ridge, NJ) zugewiesen ist
- ^ US -Patent 6115721, Nagy, Michael (Tampa, FL), "System und Methode für Datenbank-Speichern und Wiederherstellen mit Selbstbezügen", veröffentlicht 2000-09-05, zugewiesen an IBM (Armonk, NY)
- ^ "Basierte Zeiger". Msdn.microsoft.com. Abgerufen 2018-04-13.
- ^ ULF Bilting, Jan Skansholm, "Vägen bis C" (die Straße zu C), dritte Ausgabe, Seite 169, ISBN91-44-01468-6
- ^ Nick Parlante, [1], Stanford Informatik Bildungsbibliothek, S. 9–10 (2000).
- ^ ISO 7185 Pascal Standard (inoffizielle Kopie), Abschnitt 6.4.4 Zeigertypen Archiviert 2017-04-24 bei der Wayback -Maschine und anschließend.
- ^ J. Welsh, W. J. Sneserer und C. A. R. Hoare, "Ambiguitäten und Unsicherheiten in Pascal", Software: Practice and Experience 7, pp. 685–696 (1977)
- ^ Kostenloser Pascal Language Reference Guide, Abschnitt 3.4 Zeiger
- ^ Kontaktdetails. "// Referenzen machen (Perl Referenzen und verschachtelte Datenstrukturen)". Perldoc.perl.org. Abgerufen 2018-04-13.
Externe Links
- PL/I LIST -Verarbeitung Papier aus der Juni -Ausgabe von 1967 von CACM
- CDECL.org Ein Werkzeug zum Umwandeln von Zeigerdeklarationen in einfaches Englisch
- Über IQ.com Ein Anfänger -Leitfaden, der Zeiger in einem einfachen Englisch beschreibt.
- Zeiger und Erinnerung Einführung in Zeiger - Stanford Informatik Education Library
- Zeiger in der C -Programmierung Archiviert 2019-06-09 bei der Wayback -Maschine Ein visuelles Modell für Anfänger in der C -Programmierung
- 0pointer.de Eine knappe Liste von Quellcodes für Mindestlängen, die einen Nullzeiger in verschiedenen Programmiersprachen Dereference
- "Das C -Buch" - enthält Zeigerbeispiele in ANSI C
- Joint Technical Committee ISO/IEC JTC 1, Unterausschuss SC 22, Arbeitsgruppe WG 14 (2007-09-08). Internationales Standard -ISO/IEC 9899 (PDF). Ausschussentwurf.
{{}}
: Cs1 montiert: Mehrfachnamen: Autorenliste (Link).