Was genau ist ein C-Zeiger, wenn nicht eine Speicheradresse?


206

In einer seriösen Quelle über C werden nach Erörterung des &Bedieners die folgenden Informationen gegeben :

... Es ist ein bisschen bedauerlich, dass die Terminologie [Adresse von] erhalten bleibt, weil sie diejenigen verwirrt, die nicht wissen, worum es bei Adressen geht, und diejenigen irreführt, die dies tun: Das Nachdenken über Zeiger, als wären sie Adressen, führt normalerweise zu Trauer. .

Andere Materialien, die ich gelesen habe (aus ebenso seriösen Quellen, würde ich sagen), haben Zeiger und den &Operator immer unverfroren als Speicheradressen bezeichnet. Ich würde gerne weiter nach der Aktualität der Sache suchen, aber es ist schwierig, wenn seriöse Quellen anderer Meinung sind.

Jetzt bin ich etwas verwirrt - was genau ist dann ein Zeiger, wenn nicht eine Speicheradresse?

PS

Der Autor sagt später: ... Ich werde den Begriff "Adresse von" weiterhin verwenden, da es noch schlimmer wäre, einen anderen [Begriff] zu erfinden .


118
Ein Zeiger ist eine Variable , die eine Adresse enthält. Es hat auch eine eigene Adresse. Dies ist der grundlegende Unterschied zwischen einem Zeiger und einem Array. Ein Array effektiv ist eine Adresse (und implizit die Adresse ist selbst ).
WhozCraig

7
Was ist Ihre "seriöse Quelle" für das Zitat?
Cornstalks

22
Die ultimative seriöse Quelle ist der Sprachstandard und keine Bücher, die halb davon abgeleitet und halb vom Hintern des Autors gezogen sind. Ich lernte es auf die harte Tour, machte fast jeden Fehler, den ich konnte, und baute langsam ein mentales Modell von C auf, das dem des Standards etwas nahe kam, und ersetzte es schließlich durch das Modell des Standards.
Alexey Frunze

9
@thang Die Leute denken, pointer = integer, weil es oft so ist (x86 Linux und Windows "lehren" uns das), weil die Leute gerne verallgemeinern, weil die Leute den Sprachstandard nicht gut kennen und weil sie wenig Erfahrung mit radikal anderen haben Plattformen. Dieselben Personen gehen wahrscheinlich davon aus, dass ein Zeiger auf Daten und ein Zeiger auf eine Funktion ineinander konvertiert werden können und Daten als Code ausgeführt werden können und auf Code als Daten zugegriffen werden kann. Dies gilt zwar für von Neuman-Architekturen (mit 1 Adressraum), aber nicht unbedingt für Harvard-Architekturen (mit Code- und Datenräumen).
Alexey Frunze

6
@exebook Standards sind nicht für Neulinge (insbesondere für vollständige). Sie sollen keine sanften Einführungen und eine Vielzahl von Beispielen liefern. Sie definieren etwas formal, damit es von Fachleuten korrekt implementiert werden kann.
Alexey Frunze

Antworten:


148

Der C-Standard definiert nicht, was ein Zeiger intern ist und wie er intern funktioniert. Dies ist beabsichtigt, um die Anzahl der Plattformen nicht zu begrenzen, auf denen C als kompilierte oder interpretierte Sprache implementiert werden kann.

Ein Zeigerwert kann eine Art ID oder Handle oder eine Kombination mehrerer IDs sein (sagen Sie Hallo zu x86-Segmenten und Offsets) und nicht unbedingt eine echte Speicheradresse. Diese ID kann eine beliebige Textzeichenfolge mit fester Größe sein. Nicht adressierte Darstellungen können für einen C-Interpreter besonders nützlich sein.


34
Es gibt nicht viel zu erklären. Jede Variable hat ihre Adresse im Speicher. Sie müssen ihre Adressen jedoch nicht in Zeigern auf sie speichern. Stattdessen können Sie Ihre Variablen von 1 bis zu einem beliebigen Wert nummerieren und diese Nummer im Zeiger speichern. Dies ist gemäß dem Sprachstandard vollkommen legal, solange die Implementierung weiß, wie diese Zahlen in Adressen umgewandelt werden und wie Zeigerarithmetik mit diesen Zahlen und allen anderen vom Standard geforderten Dingen durchgeführt wird.
Alexey Frunze

4
Ich möchte hinzufügen, dass auf x86 eine Speicheradresse aus einem Segmentwähler und einem Offset besteht, wodurch ein Zeiger als Segment dargestellt wird: Offset verwendet immer noch die Speicheradresse.
Thang

6
@Lundin Ich habe keine Probleme, die generische Natur des Standards zu ignorieren und die nicht anwendbare, wenn ich meine Plattform und meinen Compiler kenne. Die ursprüngliche Frage ist jedoch allgemein gehalten, sodass Sie den Standard bei der Beantwortung nicht ignorieren können.
Alexey Frunze

8
@Lundin Du musst kein Revolutionär oder Wissenschaftler sein. Angenommen, Sie möchten einen 32-Bit-Computer auf einem physischen 16-Bit-Computer emulieren und Ihren 64-KB-RAM-Speicher mithilfe des Festplattenspeichers auf bis zu 4 GB erweitern und 32-Bit-Zeiger als Offsets in eine große Datei implementieren. Diese Zeiger sind keine echten Speicheradressen.
Alexey Frunze

6
Das beste Beispiel, das ich je gesehen habe, war die C-Implementierung für Symbolics Lisp Machines (ca. 1990). Jedes C-Objekt wurde als Lisp-Array implementiert, und Zeiger wurden als Paar aus einem Array und einem Index implementiert. Aufgrund der Überprüfung der Array-Grenzen von Lisp konnten Sie niemals von einem Objekt zum anderen überlaufen.
Barmar

62

Ich bin mir über Ihre Quelle nicht sicher, aber die Art der Sprache, die Sie beschreiben, stammt aus dem C-Standard:

6.5.3.2 Adress- und Indirektionsoperatoren
[...]
3. Der Operator unary & gibt die Adresse seines Operanden an. [...]

Also ... ja, Zeiger zeigen auf Speicheradressen. Zumindest schlägt der C-Standard dies so vor.

Um es etwas klarer auszudrücken, ein Zeiger ist eine Variable, die den Wert einer Adresse enthält . Die Adresse eines Objekts (das in einem Zeiger gespeichert sein kann) wird mit dem unären &Operator zurückgegeben.

Ich kann die Adresse "42 Wallaby Way, Sydney" in einer Variablen speichern (und diese Variable wäre eine Art "Zeiger", aber da dies keine Speicheradresse ist, würden wir sie nicht richtig als "Zeiger" bezeichnen). Ihr Computer verfügt über Adressen für seine Speichereimer. Zeiger speichern den Wert einer Adresse (dh ein Zeiger speichert den Wert "42 Wallaby Way, Sydney", bei dem es sich um eine Adresse handelt).

Bearbeiten: Ich möchte den Kommentar von Alexey Frunze erweitern.

Was genau ist ein Zeiger? Schauen wir uns den C-Standard an:

6.2.5 Types
[...]
20. [...]
ein Zeigertyp kann aus einem Funktionstyp oder einen Objekttyp abgeleitet werden, der angerufene referenzierten Typ . Ein Zeigertyp beschreibt ein Objekt, dessen Wert einen Verweis auf eine Entität des referenzierten Typs bereitstellt. Ein Zeigertyp, der vom referenzierten Typ T abgeleitet ist, wird manchmal als "Zeiger auf T" bezeichnet. Die Konstruktion eines Zeigertyps aus einem referenzierten Typ wird als Ableitung des Zeigertyps bezeichnet. Ein Zeigertyp ist ein vollständiger Objekttyp.

Im Wesentlichen speichern Zeiger einen Wert, der einen Verweis auf ein Objekt oder eine Funktion bereitstellt. So'ne Art. Zeiger sollen einen Wert speichern, der auf ein Objekt oder eine Funktion verweist. Dies ist jedoch nicht immer der Fall:

6.3.2.3 Zeiger
[...]
5. Eine Ganzzahl kann in einen beliebigen Zeigertyp konvertiert werden. Sofern nicht anders angegeben, ist das Ergebnis implementierungsdefiniert, möglicherweise nicht korrekt ausgerichtet, zeigt möglicherweise nicht auf eine Entität des referenzierten Typs und ist möglicherweise eine Trap-Darstellung.

Das obige Zitat besagt, dass wir eine ganze Zahl in einen Zeiger verwandeln können. Wenn wir dies tun (dh wenn wir einen ganzzahligen Wert in einen Zeiger anstelle einer bestimmten Referenz auf ein Objekt oder eine Funktion einfügen), zeigt der Zeiger möglicherweise nicht auf eine Entität vom Referenztyp (dh er liefert möglicherweise keine Verweis auf ein Objekt oder eine Funktion). Es könnte uns etwas anderes bieten. Und dies ist eine Stelle, an der Sie möglicherweise eine Art Handle oder ID in einen Zeiger stecken (dh der Zeiger zeigt nicht auf ein Objekt; er speichert einen Wert, der etwas darstellt, aber dieser Wert ist möglicherweise keine Adresse).

Also ja, wie Alexey Frunze sagt, ist es möglich, dass ein Zeiger keine Adresse für ein Objekt oder eine Funktion speichert. Es ist möglich, dass ein Zeiger stattdessen eine Art "Handle" oder ID speichert. Sie können dies tun, indem Sie einem Zeiger einen beliebigen ganzzahligen Wert zuweisen. Was dieses Handle oder diese ID darstellt, hängt vom System / der Umgebung / dem Kontext ab. Solange Ihr System / Ihre Implementierung den Wert verstehen kann, sind Sie in guter Verfassung (dies hängt jedoch vom spezifischen Wert und dem spezifischen System / der Implementierung ab).

Normalerweise speichert ein Zeiger eine Adresse für ein Objekt oder eine Funktion. Wenn keine tatsächliche Adresse (für ein Objekt oder eine Funktion) gespeichert wird, ist das Ergebnis implementierungsdefiniert (was bedeutet, dass genau das, was passiert und was der Zeiger jetzt darstellt, von Ihrem System und Ihrer Implementierung abhängt, sodass es sich möglicherweise um ein Handle oder eine ID handelt ein bestimmtes System, aber die Verwendung des gleichen Codes / Werts auf einem anderen System kann Ihr Programm zum Absturz bringen).

Das war länger als ich dachte ...


3
In einem C-Interpreter kann ein Zeiger eine Nicht-Adress-ID / Handle / etc. Enthalten.
Alexey Frunze

4
@exebook Der Standard ist ohnehin nicht auf kompiliertes C beschränkt.
Alexey Frunze

7
@ Lundin Bravo! Lassen Sie uns den Standard mehr ignorieren! Als ob wir es nicht genug ignoriert hätten und deshalb keine fehlerhafte und schlecht tragbare Software produziert hätten. Bitte beachten Sie auch nicht, dass die ursprüngliche Frage generisch ist und daher eine generische Antwort benötigt.
Alexey Frunze

3
Wenn andere sagen, dass ein Zeiger ein Handle oder etwas anderes als eine Adresse sein könnte, bedeutet dies nicht nur, dass Sie Daten in einen Zeiger zwingen können, indem Sie eine Ganzzahl in einen Zeiger umwandeln. Sie bedeuten, dass der Compiler möglicherweise etwas anderes als Speicheradressen verwendet, um Zeiger zu implementieren. Auf dem Alpha-Prozessor mit DECs ABI war ein Funktionszeiger nicht die Adresse der Funktion, sondern die Adresse eines Deskriptors einer Funktion, und der Deskriptor enthielt die Adresse der Funktion und einige Daten zu den Funktionsparametern. Der Punkt ist, dass der C-Standard sehr flexibel ist.
Eric Postpischil

5
@Lundin: Die Behauptung, dass Zeiger auf 100% der vorhandenen Computersysteme in der realen Welt als ganzzahlige Adressen implementiert sind, ist falsch. Es gibt Computer mit Wortadressierung und Segmentversatzadressierung. Es gibt immer noch Compiler mit Unterstützung für Nah- und Fernzeiger. Es gibt PDP-11-Computer mit RSX-11 und dem Task Builder und seinen Überlagerungen, in denen ein Zeiger die Informationen identifizieren muss, die zum Laden einer Funktion von der Festplatte erforderlich sind. Ein Zeiger kann nicht die Speicheradresse eines Objekts haben, wenn sich das Objekt nicht im Speicher befindet!
Eric Postpischil

39

Zeiger gegen Variable

In diesem Bild,

pointer_p ist ein Zeiger, der sich bei 0x12345 befindet und auf eine Variable variable_v bei 0x34567 zeigt.


16
Dies spricht nicht nur nicht den Begriff der Adresse im Gegensatz zum Zeiger an, sondern verfehlt auch ganzheitlich den Punkt, dass eine Adresse nicht nur eine ganze Zahl ist.
Gilles 'SO - hör auf böse zu sein'

19
-1, dies erklärt nur, was ein Zeiger ist. Das war nicht die Frage - und Sie schieben alle Komplexitäten beiseite, um die es in der Frage geht .
Alexis

34

Sich einen Zeiger als Adresse vorzustellen, ist eine Annäherung . Wie alle Annäherungen ist es gut genug, um manchmal nützlich zu sein, aber es ist auch nicht genau, was bedeutet, dass das Verlassen darauf Probleme verursacht.

Ein Zeiger ist insofern wie eine Adresse, als er angibt, wo sich ein Objekt befindet. Eine unmittelbare Einschränkung dieser Analogie besteht darin, dass nicht alle Zeiger tatsächlich eine Adresse enthalten. NULList ein Zeiger, der keine Adresse ist. Der Inhalt einer Zeigervariable kann tatsächlich eine von drei Arten sein:

  • die Adresse eines Objekts, die dereferenziert werden kann (wenn sie pdie Adresse von enthält, xhat der Ausdruck *pden gleichen Wert wie x);
  • ein Nullzeiger , von dem NULLein Beispiel ist;
  • Ungültiger Inhalt, der nicht auf ein Objekt pverweist (wenn er keinen gültigen Wert enthält, *pkann er alles tun („undefiniertes Verhalten“), wobei ein Absturz des Programms häufig vorkommt).

Darüber hinaus wäre es genauer zu sagen, dass ein Zeiger (falls gültig und nicht null) eine Adresse enthält : Ein Zeiger gibt an, wo ein Objekt zu finden ist, aber es sind mehr Informationen damit verbunden.

Insbesondere hat ein Zeiger einen Typ. Auf den meisten Plattformen hat der Typ des Zeigers zur Laufzeit keinen Einfluss, aber einen Einfluss, der über den Typ zur Kompilierungszeit hinausgeht. Wenn pes sich um einen Zeiger auf int( int *p;) handelt, p + 1zeigt es auf eine Ganzzahl, nach der sizeof(int)Bytes stehen p(vorausgesetzt, es p + 1handelt sich immer noch um einen gültigen Zeiger). Wenn qein Zeiger chardarauf auf dieselbe Adresse wie p( char *q = p;) verweist , q + 1ist er nicht dieselbe Adresse wie p + 1. Wenn Sie sich Zeiger als Adressen vorstellen, ist es nicht sehr intuitiv, dass die „nächste Adresse“ für verschiedene Zeiger auf dieselbe Position unterschiedlich ist.

In einigen Umgebungen ist es möglich, mehrere Zeigerwerte mit unterschiedlichen Darstellungen (unterschiedliche Bitmuster im Speicher) zu haben, die auf dieselbe Stelle im Speicher verweisen. Sie können sich diese als unterschiedliche Zeiger vorstellen, die dieselbe Adresse enthalten, oder als unterschiedliche Adressen für denselben Ort - die Metapher ist in diesem Fall nicht klar. Der ==Betreiber informiert Sie immer , ob die beiden Operanden an die gleiche Stelle gerichtet sind, so auf diesen Umgebungen Sie können , p == qobwohl pund qverschiedene Bitmuster haben.

Es gibt sogar Umgebungen, in denen Zeiger andere Informationen als die Adresse enthalten, z. B. Typ- oder Berechtigungsinformationen. Sie können leicht durch Ihr Leben als Programmierer gehen, ohne auf diese zu stoßen.

Es gibt Umgebungen, in denen verschiedene Arten von Zeigern unterschiedliche Darstellungen haben. Sie können sich das als verschiedene Arten von Adressen mit unterschiedlichen Darstellungen vorstellen. Beispielsweise haben einige Architekturen Byte- und Wortzeiger oder Objektzeiger und Funktionszeiger.

Alles in allem ist es nicht schlecht, Zeiger als Adressen zu betrachten, solange Sie dies berücksichtigen

  • Es sind nur gültige Zeiger ungleich Null, die Adressen sind.
  • Sie können mehrere Adressen für denselben Standort haben.
  • Sie können nicht mit Adressen rechnen, und es gibt keine Reihenfolge für sie.
  • Der Zeiger enthält auch Typinformationen.

Umgekehrt ist es weitaus schwieriger. Nicht alles, was wie eine Adresse aussieht, kann ein Zeiger sein . Irgendwo in der Tiefe wird jeder Zeiger als Bitmuster dargestellt, das als Ganzzahl gelesen werden kann, und Sie können sagen, dass diese Ganzzahl eine Adresse ist. Aber in die andere Richtung ist nicht jede ganze Zahl ein Zeiger.

Es gibt zunächst einige bekannte Einschränkungen; Beispielsweise kann eine Ganzzahl, die einen Ort außerhalb des Adressraums Ihres Programms angibt, kein gültiger Zeiger sein. Eine falsch ausgerichtete Adresse ist kein gültiger Zeiger für einen Datentyp, der ausgerichtet werden muss. Auf einer Plattform, auf der eine int4-Byte-Ausrichtung erforderlich ist, kann 0x7654321 beispielsweise kein gültiger int*Wert sein.

Es geht jedoch weit darüber hinaus, denn wenn Sie einen Zeiger auf eine Ganzzahl setzen, werden Sie in eine Welt voller Probleme geraten. Ein großer Teil dieses Problems besteht darin, dass die Optimierung von Compilern bei der Mikrooptimierung weitaus besser ist als von den meisten Programmierern erwartet, so dass ihr mentales Modell der Funktionsweise eines Programms zutiefst falsch ist. Nur weil Sie Zeiger mit derselben Adresse haben, heißt das nicht, dass sie gleichwertig sind. Betrachten Sie beispielsweise das folgende Snippet:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Sie können erwarten, dass auf einer normalen Maschine, auf der sizeof(int)==4und sizeof(short)==2, entweder 1 = 1?(Little-Endian) oder 65536 = 1?(Big-Endian) gedruckt wird . Aber auf meinem 64-Bit-Linux-PC mit GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCC ist so freundlich, uns zu warnen, was in diesem einfachen Beispiel falsch läuft - in komplexeren Beispielen bemerkt der Compiler dies möglicherweise nicht. Da phat einen anderen Typ als das &xÄndern der pPunkte, auf die die &xPunkte nicht beeinflusst werden sollen (außerhalb einiger genau definierter Ausnahmen). Daher steht es dem Compiler frei, den Wert von xin einem Register zu behalten und dieses Register nicht als *pÄnderungen zu aktualisieren . Das Programm dereferenziert zwei Zeiger auf dieselbe Adresse und erhält zwei unterschiedliche Werte!

Die Moral dieses Beispiels ist, dass es in Ordnung ist, sich einen (nicht null gültigen) Zeiger als Adresse vorzustellen, solange Sie die genauen Regeln der C-Sprache einhalten. Die Kehrseite der Medaille ist, dass die Regeln der C-Sprache kompliziert sind und es schwierig ist, ein intuitives Gefühl dafür zu bekommen, wenn Sie nicht wissen, was unter der Haube passiert. Und was unter der Haube passiert, ist, dass die Verbindung zwischen Zeigern und Adressen etwas locker ist, sowohl um „exotische“ Prozessorarchitekturen zu unterstützen als auch um die Optimierung von Compilern zu unterstützen.

Stellen Sie sich Zeiger als einen ersten Schritt in Ihrem Verständnis vor, aber folgen Sie dieser Intuition nicht zu weit.


5
+1. Andere Antworten scheinen zu übersehen, dass ein Zeiger Typinformationen enthält. Dies ist weitaus wichtiger als die Adresse / ID / was auch immer Diskussion.
undur_gongor

+1 Hervorragende Punkte zu Typinformationen. Ich bin mir nicht sicher, ob die Compiler-Beispiele korrekt sind, obwohl ... Es scheint zum Beispiel sehr unwahrscheinlich, dass dies *p = 3garantiert erfolgreich ist, wenn p nicht initialisiert wurde.
LarsH

@LarsH Du hast recht, danke, wie habe ich das geschrieben? Ich habe es durch ein Beispiel ersetzt, das sogar das überraschende Verhalten auf meinem PC demonstriert.
Gilles 'SO - hör auf böse zu sein'

1
ähm, NULL ist ((void *) 0) ..?
Aniket Inge

1
@ gnasher729 Der Nullzeiger ist ein Zeiger. NULList nicht, aber für den hier erforderlichen Detaillierungsgrad ist dies eine irrelevante Ablenkung. Selbst für die tägliche Programmierung kommt die Tatsache, dass NULLsie als etwas implementiert werden kann, das nicht „Zeiger“ sagt, nicht oft vor (hauptsächlich NULLin Richtung einer variadischen Funktion - aber auch dort, wenn Sie sie nicht umsetzen Sie gehen bereits davon aus, dass alle Zeigertypen dieselbe Darstellung haben.
Gilles 'SO - hör auf böse zu sein'

19

Ein Zeiger ist eine Variable, die die Speicheradresse HÄLT, nicht die Adresse selbst. Sie können jedoch einen Zeiger dereferenzieren - und Zugriff auf den Speicherort erhalten.

Beispielsweise:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

Das ist es. So einfach ist das.

Geben Sie hier die Bildbeschreibung ein

Ein Programm, das zeigt, was ich sage und was es ausgibt:

http://ideone.com/rcSUsb

Das Programm:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
Es kann noch mehr verwirren. Alice, kannst du eine Katze sehen? Nein, ich kann nur ein Lächeln einer Katze sehen. Wenn Sie also sagen, dass der Zeiger eine Adresse oder ein Zeiger eine Variable ist, die eine Adresse enthält, oder wenn Sie sagen, dass der Zeiger ein Name eines Konzepts ist, das sich auf die Idee einer Adresse bezieht, wie weit können Buchautoren in verwirrenden Neeeewbies gehen?
Exebook

@exebook für diejenigen, die in Zeigern gewürzt sind, ist es ganz einfach. Vielleicht hilft ein Bild?
Aniket Inge

5
Ein Zeiger enthält nicht unbedingt eine Adresse. In einem C-Interpreter könnte es etwas anderes sein, eine Art ID / Handle.
Alexey Frunze

Das "Label" oder der Variablenname ist ein Compiler / Assembler und existiert nicht auf Maschinenebene, daher denke ich nicht, dass es im Speicher erscheinen sollte.
Ben

1
@Aniket Eine Zeigervariable kann einen Zeigerwert enthalten. Sie müssen das Ergebnis von nur dann fopenin einer Variablen speichern , wenn Sie es mehrmals verwenden müssen (was fopenfast immer der Fall ist).
Gilles 'SO - hör auf böse zu sein'

16

Es ist schwierig, genau zu sagen, was die Autoren dieser Bücher genau meinen. Ob ein Zeiger eine Adresse enthält oder nicht, hängt davon ab, wie Sie eine Adresse definieren und wie Sie einen Zeiger definieren.

Nach all den Antworten zu urteilen, die geschrieben wurden, gehen einige Leute davon aus, dass (1) eine Adresse eine ganze Zahl sein muss und (2) ein Zeiger nicht virtuell sein muss, um dies in der Spezifikation nicht zu sagen. Mit diesen Annahmen enthalten Zeiger dann eindeutig nicht unbedingt Adressen.

Wir sehen jedoch, dass (2) wahrscheinlich wahr ist, (1) wahrscheinlich nicht wahr sein muss. Und was ist mit der Tatsache zu tun , dass das & gemäß der Antwort von @ CornStalks als Adresse des Betreibers bezeichnet wird? Bedeutet dies, dass die Autoren der Spezifikation beabsichtigen, dass ein Zeiger eine Adresse enthält?

Können wir also sagen, der Zeiger enthält eine Adresse, aber eine Adresse muss keine Ganzzahl sein? Vielleicht.

Ich denke, das alles ist jibberisches pedantisches semantisches Gerede. Es ist praktisch völlig wertlos. Können Sie sich einen Compiler vorstellen, der Code so generiert, dass der Wert eines Zeigers keine Adresse ist? Wenn ja, was? Das ist was ich dachte...

Ich denke, der Autor des Buches (der erste Auszug, der behauptet, dass Zeiger nicht unbedingt nur Adressen sind) bezieht sich wahrscheinlich auf die Tatsache, dass ein Zeiger die inhärenten Typinformationen enthält.

Beispielsweise,

 int x;
 int* y = &x;
 char* z = &x;

sowohl y als auch z sind Zeiger, aber y + 1 und z + 1 sind unterschiedlich. Wenn es sich um Speicheradressen handelt, geben Ihnen diese Ausdrücke dann nicht den gleichen Wert?

Und hier in Lügen führt das Denken über Zeiger, als wären sie Adressen, normalerweise zu Trauer . Es wurden Fehler geschrieben, weil die Leute über Zeiger nachdenken, als wären sie Adressen , und dies führt normalerweise zu Trauer .

55555 ist wahrscheinlich kein Zeiger, obwohl es eine Adresse sein kann, aber (int *) 55555 ist ein Zeiger. 55555 + 1 = 55556, aber (int *) 55555 + 1 ist 55559 (+/- Unterschied in Bezug auf die Größe von (int)).


1
+1 für den Hinweis auf Zeigerarithmetik ist nicht dasselbe wie Arithmetik für Adressen.
kutschkem

Im Fall des 16-Bit-8086 wird eine Speicheradresse durch eine Segmentbasis + Offset beschrieben, beide 16 Bit. Es gibt viele Kombinationen von Segmentbasis + Offset, die dieselbe Adresse im Speicher ergeben. Dieser farZeiger ist nicht nur "eine ganze Zahl".
vonbrand

@vonbrand Ich verstehe nicht, warum du diesen Kommentar gepostet hast. Dieses Problem wurde als Kommentar unter anderen Antworten erörtert. Fast jede andere Antwort geht davon aus, dass Adresse = Ganzzahl und alles, was nicht Ganzzahl ist, keine Adresse ist. Ich weise einfach darauf hin und stelle fest, dass es richtig sein kann oder nicht. Mein ganzer Punkt in der Antwort ist, dass es nicht relevant ist. Es ist alles nur pedantisch und das Hauptproblem wird in den anderen Antworten nicht angesprochen.
Thang

@tang, die Idee "Zeiger == Adresse" ist falsch . Dass alle und ihre Lieblingstante dies weiterhin sagen, macht es nicht richtig.
vonbrand

@vonbrand, und warum hast du diesen Kommentar unter meinem Beitrag gemacht? Ich habe nicht gesagt, dass es richtig oder falsch ist. Tatsächlich ist es in bestimmten Szenarien / Annahmen richtig, aber nicht immer. Lassen Sie mich den Punkt des Beitrags (zum zweiten Mal) noch einmal zusammenfassen. Mein ganzer Punkt in der Antwort ist, dass es nicht relevant ist. Es ist alles nur pedantisch und das Hauptproblem wird in den anderen Antworten nicht angesprochen. Es wäre angemessener, die Antworten zu kommentieren, die die Behauptung aufstellen, dass Zeiger == Adresse oder Adresse == Ganzzahl. Siehe meine Kommentare unter Alexeys Beitrag in Bezug auf Segment: Offset.
Thang

15

Nun, ein Zeiger ist eine Abstraktion, die einen Speicherort darstellt. Beachten Sie, dass das Zitat nicht besagt, dass es falsch ist, an Zeiger zu denken, als wären sie Speicheradressen, sondern nur, dass dies "normalerweise zu Trauer führt". Mit anderen Worten, es führt zu falschen Erwartungen.

Die wahrscheinlichste Quelle der Trauer ist sicherlich die Zeigerarithmetik, die tatsächlich eine der Stärken von C ist. Wenn ein Zeiger eine Adresse wäre, würde man erwarten, dass die Zeigerarithmetik eine Adressarithmetik ist. aber es ist nicht. Wenn Sie beispielsweise einer Adresse 10 hinzufügen, erhalten Sie eine Adresse, die um 10 Adressierungseinheiten größer ist. Durch Hinzufügen von 10 zu einem Zeiger wird dieser jedoch um das 10-fache der Art des Objekts erhöht, auf das er zeigt (und nicht einmal die tatsächliche Größe, sondern auf eine Ausrichtungsgrenze aufgerundet). Bei einer int *normalen Architektur mit 32-Bit-Ganzzahlen würde das Hinzufügen von 10 zu 40 Adressierungseinheiten (Bytes) inkrementieren. Erfahrene C-Programmierer sind sich dessen bewusst und leben damit, aber Ihr Autor ist offensichtlich kein Fan von schlampigen Metaphern.

Es gibt die zusätzliche Frage, wie der Inhalt des Zeigers repräsentiert den Speicherplatz: Wie viele Antworten erklärt hat, eine Adresse ist nicht immer ein int (oder lang). In einigen Architekturen ist eine Adresse ein "Segment" plus ein Offset. Ein Zeiger kann sogar nur den Versatz in das aktuelle Segment enthalten ("nahe" Zeiger), der für sich genommen keine eindeutige Speicheradresse ist. Und der Zeigerinhalt hat möglicherweise nur eine indirekte Beziehung zu einer Speicheradresse, wie die Hardware sie versteht. Aber der Autor des zitierten Zitats erwähnt nicht einmal die Repräsentation, daher denke ich, dass sie eher an konzeptionelle Äquivalenz als an Repräsentation gedacht hatten.


12

So habe ich es in der Vergangenheit einigen verwirrten Personen erklärt: Ein Zeiger hat zwei Attribute, die sich auf sein Verhalten auswirken. Es hat einen Wert , der (in typischen Umgebungen) eine Speicheradresse ist, und einen Typ , der den Typ und die Größe des Objekts angibt , auf das es zeigt.

Zum Beispiel gegeben:

union {
    int i;
    char c;
} u;

Sie können drei verschiedene Zeiger haben, die alle auf dasselbe Objekt zeigen:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Wenn Sie die Werte dieser Zeiger vergleichen, sind sie alle gleich:

v==i && i==c

Wenn Sie jedoch jeden Zeiger inkrementieren, werden Sie feststellen, dass der Typ , auf den sie zeigen, relevant wird.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Die Variablen iund cwerden an diesem Punkt verschiedene Werte haben, weil i++Ursachen , idie Adresse der nächsten zugängliche ganze Zahl zu enthalten, und c++führen czu Punkt zum nächsten adressierbaren Charakter. In der Regel belegen Ganzzahlen mehr Speicher als Zeichen und haben daher ieinen größeren Wert als cnach dem Inkrementieren beider Zeichen .


2
+1 Danke. Mit Zeigern sind Wert und Typ so untrennbar miteinander verbunden, wie man den Körper des Menschen von seiner Seele trennen kann.
Aki Suihkonen

i == cist schlecht geformt (Sie können Zeiger nur dann mit verschiedenen Typen vergleichen, wenn eine implizite Konvertierung von einem zum anderen erfolgt). Wenn Sie dies mit einer Besetzung beheben, bedeutet dies, dass Sie eine Konvertierung angewendet haben. Anschließend ist fraglich, ob die Konvertierung den Wert ändert oder nicht. (Sie könnten behaupten, dass dies nicht der Fall ist, aber das ist nur das Gleiche, was Sie mit diesem Beispiel beweisen wollten).
MM

8

Mark Bessey hat es bereits gesagt, aber dies muss noch einmal betont werden, bis es verstanden wird.

Zeiger hat genauso viel mit einer Variablen zu tun wie mit einem Literal 3.

Der Zeiger ist ein Tupel aus einem Wert (einer Adresse) und einem Typ (mit zusätzlichen Eigenschaften, z. B. schreibgeschützt). Der Typ (und gegebenenfalls die zusätzlichen Parameter) können den Kontext weiter definieren oder einschränken. z.B. __far ptr, __near ptr: Was ist der Kontext der Adresse: Stapel, Heap, lineare Adresse, Versatz von irgendwoher, physischer Speicher oder was.

Es ist die Eigenschaft vom Typ , die die Zeigerarithmetik ein wenig von der Ganzzahlarithmetik unterscheidet.

Die Zählerbeispiele für einen Zeiger, der keine Variable ist, sind zu viele, um sie zu ignorieren

  • fopen gibt einen FILE-Zeiger zurück. (Wo ist die Variable?)

  • Stapelzeiger oder Rahmenzeiger sind typischerweise nicht adressierbare Register

    *(int *)0x1231330 = 13; - Umwandlung eines beliebigen Ganzzahlwerts in einen Zeiger vom Typ "Ganzzahl" und Schreiben / Lesen einer Ganzzahl, ohne jemals eine Variable einzuführen

Während der Lebensdauer eines C-Programms gibt es viele andere Instanzen von temporären Zeigern, die keine Adressen haben - und daher keine Variablen, sondern Ausdrücke / Werte mit einem der Kompilierungszeit zugeordneten Typ.


8

Du hast recht und bist gesund. Normalerweise ist ein Zeiger nur eine Adresse, sodass Sie ihn in eine Ganzzahl umwandeln und beliebige Arithmetiken ausführen können.

Aber manchmal sind Zeiger nur ein Teil einer Adresse. Bei einigen Architekturen wird ein Zeiger in eine Adresse mit zusätzlicher Basis konvertiert oder ein anderes CPU- Register verwendet.

Heutzutage ist es auf einer PC- und ARM- Architektur mit einem flachen Speichermodell und einer C-Sprache, die nativ kompiliert wurde, in Ordnung zu glauben, dass ein Zeiger eine ganzzahlige Adresse zu einem Ort im eindimensional adressierbaren RAM ist.


PC ... Flachspeichermodell? Was sind Selektoren?
Thang

Riight. Und wenn die nächste Architekturänderung bevorsteht, möglicherweise mit separaten Code- und Datenbereichen, oder wenn jemand zur ehrwürdigen Segmentarchitektur zurückkehrt (was aus Sicherheitsgründen sehr sinnvoll ist und möglicherweise sogar einen Schlüssel zur Segmentnummer + zum Offset hinzufügt, um die Berechtigungen zu überprüfen) schöne "Zeiger sind nur ganze Zahlen" stürzt ab.
vonbrand

7

Ein Zeiger ist wie jede andere Variable in C im Grunde eine Sammlung von Bits, die durch einen oder mehrere verkettete unsigned charWerte dargestellt werden können (wie bei jeder anderen Art von Cariable, sizeof(some_variable)gibt die Anzahl der unsigned charWerte an). Was einen Zeiger von anderen Variablen unterscheidet, ist, dass ein C-Compiler die Bits in einem Zeiger so interpretiert, dass er irgendwie einen Ort identifiziert, an dem eine Variable gespeichert werden kann. In C ist es im Gegensatz zu einigen anderen Sprachen möglich, Speicherplatz für mehrere Variablen anzufordern und dann einen Zeiger auf einen beliebigen Wert in dieser Menge in einen Zeiger auf eine andere Variable in dieser Menge umzuwandeln.

Viele Compiler implementieren Zeiger, indem sie ihre Bits verwenden, um die tatsächlichen Maschinenadressen zu speichern. Dies ist jedoch nicht die einzig mögliche Implementierung. Eine Implementierung könnte ein Array - für den Benutzercode nicht zugänglich - behalten, das die Hardwareadresse und die zugewiesene Größe aller von einem Programm verwendeten Speicherobjekte (Sätze von Variablen) auflistet, und jeden Zeiger einen Index in einem Array enthalten lassen mit einem Versatz von diesem Index. Ein solches Design würde es einem System ermöglichen, den Code nicht nur darauf zu beschränken, nur mit dem Speicher zu arbeiten, den es besitzt, sondern auch sicherzustellen, dass ein Zeiger auf ein Speicherelement nicht versehentlich in einen Zeiger auf ein anderes Speicherelement konvertiert werden kann (in einem System, das Hardware verwendet Adressen, wenn foound barArrays von 10 Elementen sind, die nacheinander im Speicher gespeichert sind, ein Zeiger auf das "elfte" Element vonfookönnte stattdessen auf das erste Element von zeigen bar, aber in einem System, in dem jeder "Zeiger" eine Objekt-ID und ein Offset ist, könnte das System abfangen, wenn Code versucht, einen Zeiger auf einen Wert fooaußerhalb seines zugewiesenen Bereichs zu indizieren . Es wäre für ein solches System auch möglich, Speicherfragmentierungsprobleme zu beseitigen, da die physischen Adressen, die Zeigern zugeordnet sind, verschoben werden könnten.

Beachten Sie, dass Zeiger zwar etwas abstrakt sind, aber nicht abstrakt genug, um es einem vollständig standardkonformen C-Compiler zu ermöglichen, einen Garbage Collector zu implementieren. Der C-Compiler gibt an, dass jede Variable, einschließlich Zeiger, als Folge von unsigned charWerten dargestellt wird. Wenn eine Variable gegeben ist, kann man sie in eine Folge von Zahlen zerlegen und diese Folge später wieder in eine Variable des ursprünglichen Typs umwandeln. Folglich wäre es für ein Programm möglichcallocEinige Speicher (die einen Zeiger darauf erhalten), speichern dort etwas, zerlegen den Zeiger in eine Reihe von Bytes, zeigen diese auf dem Bildschirm an und löschen dann alle Verweise darauf. Wenn das Programm dann einige Zahlen von der Tastatur akzeptierte, diese zu einem Zeiger rekonstituierte und dann versuchte, Daten von diesem Zeiger zu lesen, und wenn der Benutzer dieselben Zahlen eingab, die das Programm zuvor angezeigt hatte, musste das Programm die Daten ausgeben das war im calloced-Speicher gespeichert worden . Da es keine denkbare Möglichkeit gibt, dass der Computer wissen könnte, ob der Benutzer eine Kopie der angezeigten Nummern erstellt hat, wäre es nicht denkbar, dass der Computer wissen könnte, ob in Zukunft jemals auf den oben genannten Speicher zugegriffen werden könnte.


Bei hohem Overhead könnten Sie möglicherweise eine Verwendung des Zeigerwerts erkennen, die seinen numerischen Wert "verlieren" könnte, und die Zuordnung anheften, damit der Garbage Collector ihn nicht sammelt oder verschiebt (es freesei denn, dies wird natürlich explizit aufgerufen). Ob die resultierende Implementierung allzu nützlich wäre, ist eine andere Frage, da ihre Fähigkeit zum Sammeln möglicherweise zu eingeschränkt ist, aber Sie könnten es zumindest als Garbage Collector bezeichnen :-) Zeigerzuweisung und Arithmetik würden den Wert nicht "verlieren", aber Jeder Zugang zu einem char*unbekannten Ursprung müsste überprüft werden.
Steve Jessop

@SteveJessop: Ich denke, ein solches Design wäre schlimmer als nutzlos, da der Code nicht wissen kann, welche Zeiger freigegeben werden müssen. Garbage Collectors, die davon ausgehen, dass alles, was wie ein Zeiger aussieht, zu konservativ ist, können jedoch im Allgemeinen geändert werden, um "permanente" Speicherlecks zu vermeiden. Jede Aktion, die aussieht, als würde ein Zeiger in Bytes zerlegt, um den Zeiger dauerhaft einzufrieren, ist ein garantiertes Rezept für Speicherlecks.
Supercat

Ich denke, es würde sowieso aus Leistungsgründen fehlschlagen - wenn Sie möchten, dass Ihr Code so langsam ausgeführt wird, weil jeder Zugriff überprüft wird, schreiben Sie ihn nicht in C ;-) Ich habe größere Hoffnungen auf den Einfallsreichtum von C-Programmierern als Sie, da ich denke, dass es unpraktisch ist, ist es wahrscheinlich nicht unplausibel, unnötige Zuordnungen zu vermeiden. Wie auch immer, C ++ definiert "sicher abgeleitete Zeiger" genau, um dieses Problem zu lösen. Wir wissen also, was zu tun ist, wenn wir die Abstraktheit von C-Zeigern jemals auf das Niveau erhöhen möchten, auf dem sie eine einigermaßen effektive Speicherbereinigung unterstützen.
Steve Jessop

@SteveJessop: Damit ein GC-System nützlich ist, sollte es entweder in der Lage sein, Speicher, auf den freenicht aufgerufen wurde, zuverlässig freizugeben , oder verhindern, dass ein Verweis auf ein freigegebenes Objekt zu einem Verweis auf ein lebendes Objekt wird [selbst wenn Ressourcen verwendet werden, die dies erfordern explizite Lebensdauerverwaltung, GC kann die letztere Funktion weiterhin sinnvoll ausführen]; Ein GC-System, das Objekte manchmal fälschlicherweise als Live-Referenzen betrachtet, kann verwendet werden, wenn die Wahrscheinlichkeit, dass N Objekte unnötig gleichzeitig fixiert werden, gegen Null geht, wenn N groß wird . Es sei denn, man ist bereit, einen Compilerfehler zu
melden

... für Code, der C ++ gültig ist, für den der Compiler jedoch nicht nachweisen kann, dass ein Zeiger niemals in eine nicht erkennbare Form konvertiert werden kann, sehe ich nicht, wie man das Risiko vermeiden kann, dass ein Programm tatsächlich niemals Verwendet Zeiger, da Ganzzahlen fälschlicherweise als solche angesehen werden können.
Supercat

6

Ein Zeiger ist ein Variablentyp, der in C / C ++ nativ verfügbar ist und eine Speicheradresse enthält. Wie jede andere Variable hat sie eine eigene Adresse und belegt Speicher (die Menge ist plattformspezifisch).

Ein Problem, das Sie als Ergebnis der Verwirrung sehen werden, ist der Versuch, den Referenten innerhalb einer Funktion zu ändern, indem Sie einfach den Zeiger als Wert übergeben. Dadurch wird eine Kopie des Zeigers im Funktionsbereich erstellt, und alle Änderungen an der Stelle, an der dieser neue Zeiger "zeigt", ändern nicht den Verweis des Zeigers auf den Bereich, der die Funktion aufgerufen hat. Um den tatsächlichen Zeiger innerhalb einer Funktion zu ändern, würde man normalerweise einen Zeiger an einen Zeiger übergeben.


1
Im Allgemeinen ist es ein Handle / ID. Normalerweise ist es eine einfache Adresse.
Alexey Frunze

Ich habe meine Antwort so angepasst, dass sie etwas mehr PC für die Definition von Handle in Wikipedia ist. Ich beziehe mich gerne auf Zeiger als eine bestimmte Instanz eines Handles, da ein Handle einfach eine Referenz auf einen Zeiger sein kann.
Matthew Sanders

6

KURZE ZUSAMMENFASSUNG (die ich auch oben einfügen werde):

(0) Zeiger als Adressen zu betrachten, ist oft ein gutes Lernwerkzeug und oft die eigentliche Implementierung für Zeiger auf gewöhnliche Datentypen.

(1) Bei vielen, vielleicht den meisten, Compilern sind Zeiger auf Funktionen keine Adressen, sondern größer als eine Adresse (normalerweise 2x, manchmal mehr) oder tatsächlich Zeiger auf eine Struktur im Speicher, die die Adressen von Funktionen und Ähnlichem enthält ein ständiger Pool.

(2) Zeiger auf Datenelemente und Zeiger auf Methoden sind oft noch seltsamer.

(3) Legacy-x86-Code mit FAR- und NEAR-Zeigerproblemen

(4) Mehrere Beispiele, insbesondere IBM AS / 400, mit sicheren "Fettzeigern".

Ich bin sicher, Sie können mehr finden.

DETAIL:

UMMPPHHH !!!!! Viele der bisherigen Antworten sind ziemlich typische "Programmer Weenie" -Antworten - aber keine Compiler Weenie oder Hardware Weenie. Da ich vorgebe, ein Hardware-Weenie zu sein und oft mit Compiler-Weenies arbeite, möchte ich meine zwei Cent einwerfen:

Bei vielen, wahrscheinlich den meisten C-Compilern ist ein Zeiger auf Daten vom Typ Ttatsächlich die Adresse von T.

Fein.

Aber selbst auf vielen dieser Compiler sind bestimmte Zeiger KEINE Adressen. Sie können dies erkennen, indem Sie sich ansehen sizeof(ThePointer).

Zum Beispiel sind Zeiger auf Funktionen manchmal viel größer als gewöhnliche Adressen. Oder sie können eine Indirektionsebene beinhalten. Dieser Beitragbietet eine Beschreibung, die den Intel Itanium-Prozessor betrifft, aber ich habe andere gesehen. Um eine Funktion aufzurufen, müssen Sie normalerweise nicht nur die Adresse des Funktionscodes kennen, sondern auch die Adresse des Konstantenpools der Funktion - ein Speicherbereich, aus dem Konstanten mit einem einzelnen Ladebefehl geladen werden, anstatt dass der Compiler generieren muss eine 64-Bit-Konstante aus mehreren Load Immediate- und Shift- und OR-Anweisungen. Anstelle einer einzelnen 64-Bit-Adresse benötigen Sie also 2 64-Bit-Adressen. Einige ABIs (Application Binary Interfaces) verschieben dies als 128 Bit, während andere eine Indirektionsebene verwenden, wobei der Funktionszeiger tatsächlich die Adresse eines Funktionsdeskriptors ist, der die beiden gerade erwähnten tatsächlichen Adressen enthält. Welches ist besser? Hängt von Ihrer Sichtweise ab: Leistung, Codegröße, und einige Kompatibilitätsprobleme - häufig geht der Code davon aus, dass ein Zeiger auf eine lange oder eine lange Länge umgewandelt werden kann, kann aber auch davon ausgehen, dass die lange Länge genau 64 Bit beträgt. Ein solcher Code ist möglicherweise nicht standardkonform, aber Kunden möchten möglicherweise, dass er funktioniert.

Viele von uns haben schmerzhafte Erinnerungen an die alte segmentierte Intel x86-Architektur mit NEAR POINTERs und FAR POINTERS. Zum Glück sind diese mittlerweile fast ausgestorben, daher nur eine kurze Zusammenfassung: Im 16-Bit-Real-Modus war die tatsächliche lineare Adresse

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Im geschützten Modus könnte dies der Fall sein

LinearAddress = SegmentRegister[SegNum].base + offset

Die resultierende Adresse wird anhand eines im Segment festgelegten Grenzwerts überprüft. Einige Programme verwendeten nicht wirklich Standard-C / C ++ FAR- und NEAR-Zeigerdeklarationen, aber viele sagten nur *T---, aber es gab Compiler- und Linker-Schalter, so dass beispielsweise Codezeiger in der Nähe von Zeigern sein könnten, nur ein 32-Bit-Offset gegenüber dem, was sich darin befindet das CS-Register (Code Segment), während die Datenzeiger möglicherweise FAR-Zeiger sind, wobei sowohl eine 16-Bit-Segmentnummer als auch ein 32-Bit-Offset für einen 48-Bit-Wert angegeben werden. Nun, diese beiden Größen hängen sicherlich mit der Adresse zusammen, aber da sie nicht die gleiche Größe haben, welche von ihnen ist die Adresse? Darüber hinaus enthielten die Segmente neben Informationen zur tatsächlichen Adresse auch Berechtigungen - schreibgeschützt, schreibgeschützt, ausführbar.

Ein interessanteres Beispiel, IMHO, ist (oder war vielleicht) die IBM AS / 400-Familie. Dieser Computer war einer der ersten, der ein Betriebssystem in C ++ implementiert hat. Zeiger auf diese Machime waren normalerweise 2X die tatsächliche Adressgröße - z. B. wie in dieser Präsentationsagt, 128-Bit-Zeiger, aber die tatsächlichen Adressen waren 48-64 Bit, und wieder einige zusätzliche Informationen, was als Funktion bezeichnet wird, die Berechtigungen wie Lesen, Schreiben sowie eine Begrenzung zur Verhinderung eines Pufferüberlaufs bereitstellten. Ja, Sie können dies kompatibel mit C / C ++ tun - und wenn dies allgegenwärtig wäre, würden sich die chinesische PLA und die slawische Mafia nicht in so viele westliche Computersysteme hacken. In der Vergangenheit hat die meiste C / C ++ - Programmierung jedoch die Sicherheit für die Leistung vernachlässigt. Am interessantesten ist, dass die AS400-Familie es dem Betriebssystem ermöglichte, sichere Zeiger zu erstellen, die für nicht privilegierten Code vergeben werden konnten, die der nicht privilegierte Code jedoch nicht fälschen oder manipulieren konnte. Auch hier funktioniert die Sicherheit und der standardkonforme, viel schlampige, nicht standardkonforme C / C ++ - Code in einem so sicheren System nicht. Auch hier gibt es offizielle Standards,

Jetzt werde ich meine Sicherheits-Seifenkiste verlassen und einige andere Arten erwähnen, in denen Zeiger (verschiedener Typen) oft nicht wirklich Adressen sind: Zeiger auf Datenelemente, Zeiger auf Elementfunktionsmethoden und deren statische Versionen sind größer als eine gewöhnliche Adresse. Wie dieser Beitrag sagt:

Es gibt viele Möglichkeiten, dies zu lösen [Probleme im Zusammenhang mit einfacher oder mehrfacher Inheitanz und virtueller Vererbung]. Der Visual Studio-Compiler entscheidet sich dafür: Ein Zeiger auf eine Elementfunktion einer mehrfach vererbten Klasse ist wirklich eine Struktur. "Und sie sagen weiter:" Das Umwandeln eines Funktionszeigers kann seine Größe ändern! ".

Wie Sie wahrscheinlich anhand meiner Überlegungen zur (In-) Sicherheit erraten können, war ich an C / C ++ - Hardware- / Softwareprojekten beteiligt, bei denen ein Zeiger eher wie eine Funktion als wie eine Rohadresse behandelt wurde.

Ich könnte weitermachen, aber ich hoffe, Sie haben die Idee.

KURZE ZUSAMMENFASSUNG (die ich auch oben einfügen werde):

(0) Das Betrachten von Zeigern als Adressen ist oft ein gutes Lernwerkzeug und oft die eigentliche Implementierung für Zeiger auf gewöhnliche Datentypen.

(1) Bei vielen, vielleicht den meisten, Compilern sind Zeiger auf Funktionen keine Adressen, sondern größer als eine Adresse (normalerweise 2X, manchmal mehr) oder tatsächlich Zeiger auf eine Struktur im Speicher, die die Adressen von Funktionen und Ähnlichem enthält ein ständiger Pool.

(2) Zeiger auf Datenelemente und Zeiger auf Methoden sind oft noch seltsamer.

(3) Legacy-x86-Code mit FAR- und NEAR-Zeigerproblemen

(4) Mehrere Beispiele, insbesondere IBM AS / 400, mit sicheren "Fettzeigern".

Ich bin sicher, Sie können mehr finden.


Im 16-Bit-Real-Modus LinearAddress = SegmentRegister.Selector * 16 + Offset(Notenzeiten 16, nicht um 16 verschieben). Im geschützten Modus LinearAddress = SegmentRegister.base + offset(keine Multiplikation jeder Art, die Segment - Basis ist in dem GDT / LDT und zwischengespeichert in dem Segmentregister gespeichert wie ).
Alexey Frunze

Sie haben auch Recht mit der Segmentbasis. Ich hatte mich falsch erinnert. Es ist das Segmentlimit, das optional mit 4K multipliziert wird. Die Segmentbasis muss nur von der Hardware entschlüsselt werden, wenn ein Segmentdeskriptor aus dem Speicher in ein Segmentregister geladen wird.
Krazy Glew

4

Ein Zeiger ist nur eine andere Variable, die verwendet wird, um die Adresse eines Speicherorts zu speichern (normalerweise die Speicheradresse einer anderen Variablen).


Der Pointee ist also tatsächlich eine Speicheradresse? Sie sind mit dem Autor nicht einverstanden? Ich versuche nur zu verstehen.
d0rmLife

Die Hauptfunktion des Zeigers besteht darin, auf etwas zu zeigen. Wie genau das erreicht wird und ob es eine echte Adresse gibt oder nicht, ist nicht definiert. Ein Zeiger kann nur eine ID / ein Handle sein, keine echte Adresse.
Alexey Frunze

4

Sie können es so sehen. Ein Zeiger ist ein Wert, der eine Adresse im adressierbaren Speicherbereich darstellt.


2
Ein Zeiger muss nicht unbedingt die reale Speicheradresse enthalten. Siehe meine Antwort und die Kommentare darunter.
Alexey Frunze

Was ... Der Zeiger auf die erste Variable auf dem Stapel gibt nicht 0 aus. Er gibt den oberen (oder unteren) Teil des Stapelrahmens aus, je nachdem, wie er implementiert ist.
Thang

@thang Für die erste Variable sind oben und unten gleich. Und wie lautet die Adresse von oben oder unten in diesem Fall des Stapels?
Valentin Radu

@ ValentinRadu, warum versuchst du es nicht? Offensichtlich hast du es nicht probiert.
Thang

2
@thang Du hast recht, ich habe einige wirklich schlechte Annahmen gemacht, zu meiner Verteidigung ist 5 Uhr morgens hier.
Valentin Radu

3

Ein Zeiger ist nur eine andere Variable, die normalerweise die Speicheradresse einer anderen Variablen enthalten kann. Ein Zeiger, der eine Variable ist, hat auch eine Speicheradresse.


1
Nicht unbedingt eine Adresse. Übrigens, haben Sie vorhandene Antworten und Kommentare gelesen, bevor Sie Ihre Antwort veröffentlicht haben?
Alexey Frunze

3

Der Wechselstromzeiger ist einer Speicheradresse sehr ähnlich, jedoch mit weg abstrahierten maschinenabhängigen Details sowie einigen Funktionen, die im Befehlssatz der unteren Ebene nicht enthalten sind.

Zum Beispiel ist ein C-Zeiger relativ reich typisiert. Wenn Sie einen Zeiger durch ein Array von Strukturen inkrementieren, springt er gut von einer Struktur zur anderen.

Zeiger unterliegen Konvertierungsregeln und ermöglichen die Überprüfung des Typs der Kompilierungszeit.

Es gibt einen speziellen "Nullzeiger" -Wert, der auf Quellcodeebene portierbar ist, dessen Darstellung jedoch unterschiedlich sein kann. Wenn Sie einem Zeiger eine Ganzzahlkonstante zuweisen, deren Wert Null ist, nimmt dieser Zeiger den Nullzeigerwert an. Das Gleiche gilt, wenn Sie einen Zeiger auf diese Weise initialisieren.

Ein Zeiger kann als boolesche Variable verwendet werden: Er testet true, wenn er nicht null ist, und false, wenn er null ist.

Wenn der Nullzeiger in einer Maschinensprache eine lustige Adresse wie 0xFFFFFFFF ist, müssen Sie möglicherweise explizite Tests für diesen Wert durchführen. C versteckt das vor dir. Auch wenn der Nullzeiger 0xFFFFFFFF ist, können Sie ihn mit testen if (ptr != 0) { /* not null! */}.

Die Verwendung von Zeigern, die das Typsystem untergraben, führt zu undefiniertem Verhalten, während ähnlicher Code in der Maschinensprache möglicherweise gut definiert ist. Assembler stellen die von Ihnen geschriebenen Anweisungen zusammen, C-Compiler optimieren jedoch unter der Annahme, dass Sie nichts falsch gemacht haben. Wenn ein float *pZeiger auf eine long nVariable zeigt und *p = 0.0ausgeführt wird, muss der Compiler dies nicht verarbeiten. Eine nachfolgende Verwendung von nwird das Bitmuster des Float-Werts nicht unbedingt lesen, aber möglicherweise handelt es sich um einen optimierten Zugriff, der auf der Annahme des "strengen Aliasing" basiert, ndie nicht berührt wurde! Das heißt, die Annahme, dass sich das Programm gut verhält und daher pnicht darauf hinweisen sollte n.

In C sind Zeiger auf Code und Zeiger auf Daten unterschiedlich, aber auf vielen Architekturen sind die Adressen gleich. Es können C-Compiler entwickelt werden, die "fette" Zeiger haben, obwohl die Zielarchitektur dies nicht tut. Fette Zeiger bedeuten, dass Zeiger nicht nur Maschinenadressen sind, sondern auch andere Informationen enthalten, z. B. Informationen über die Größe des Objekts, auf das gezeigt wird, um die Grenzen zu überprüfen. Portabel geschriebene Programme lassen sich leicht auf solche Compiler portieren.

Sie sehen also, dass es viele semantische Unterschiede zwischen Maschinenadressen und C-Zeigern gibt.


NULL-Zeiger funktionieren nicht so, wie Sie denken, dass sie auf allen Plattformen funktionieren - siehe meine Antwort auf CiscoIPPhone oben. NULL == 0 ist eine Annahme, die nur auf x86-basierten Plattformen gilt. Laut Konvention sollten neue Plattformen mit x86 übereinstimmen, insbesondere in der eingebetteten Welt ist dies jedoch nicht der Fall. Bearbeiten: Außerdem unternimmt C nichts, um den Wert eines Zeigers von der Hardware zu abstrahieren - "ptr! = 0" funktioniert nicht als NULL-Test auf einer Plattform mit NULL! = 0.
DX-MON

1
DX-MON, das ist für Standard C völlig falsch. NULL ist auf 0 festgelegt und kann in Anweisungen austauschbar verwendet werden. Ob die NULL-Zeigerdarstellung in der Hardware nicht alle 0 Bits ist, spielt für die Darstellung im Quellcode keine Rolle.
Mark Bessey

@ DX-MON Ich fürchte, Sie arbeiten nicht mit den richtigen Fakten. In C dient ein integraler Konstantenausdruck als Nullzeigerkonstante, unabhängig davon, ob der Nullzeiger die Nulladresse ist. Wenn Sie einen C-Compiler kennen, bei dem ptr != 0es sich nicht um einen Nulltest handelt, geben Sie bitte dessen Identität an (aber senden Sie vorher einen Fehlerbericht an den Anbieter).
Kaz

Ich verstehe, worauf Sie hinaus wollen, aber Ihre Kommentare zu Nullzeigern sind inkohärent, weil Sie Zeiger und Speicheradressen verwirren - genau das, was das in der Frage zitierte Zitat zu vermeiden empfiehlt! Die richtige Aussage: C definiert den Nullzeiger als Null, unabhängig davon, ob eine Speicheradresse mit Offset Null zulässig ist oder nicht.
Alexis

1
@alexis Kapitel und Vers, bitte. C definiert den Nullzeiger nicht als Null. C definiert Null (oder einen beliebigen Integralkonstantenausdruck, dessen Wert Null ist) als Syntax zur Bezeichnung einer Nullzeigerkonstante. faqs.org/faqs/C-faq/faq (Abschnitt 5).
Kaz

3

Bevor wir Zeiger verstehen, müssen wir Objekte verstehen. Objekte sind Entitäten, die existieren und einen Standortbezeichner haben, der als Adresse bezeichnet wird. Ein Zeiger ist nur eine Variable wie alle anderen Variablen Cmit einem Typ namens, pointerdessen Inhalt als Adresse eines Objekts interpretiert wird, das die folgende Operation unterstützt.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Ein Zeiger wird basierend auf dem Objekttyp klassifiziert, auf den er sich gerade bezieht. Der einzige Teil der Informationen, auf die es ankommt, ist die Größe des Objekts.

Jedes Objekt unterstützt eine Operation &(Adresse von), die den Standortbezeichner (Adresse) des Objekts als Zeigerobjekttyp abruft. Dies sollte die Verwirrung um die Nomenklatur verringern, da dies sinnvoll wäre, &als Operation eines Objekts und nicht als Zeiger aufzurufen, dessen resultierender Typ ein Zeiger des Objekttyps ist.

Hinweis In dieser Erklärung habe ich das Konzept des Gedächtnisses weggelassen.


Ich mag Ihre Erklärung zur abstrakten Realität eines allgemeinen Zeigers in einem allgemeinen System. Aber vielleicht wäre es hilfreich, über das Gedächtnis zu sprechen. Wenn ich für mich selbst spreche, weiß ich, dass es ...! Ich denke, die Diskussion über den Zusammenhang kann sehr hilfreich sein, um das Gesamtbild zu verstehen. +1 sowieso :)
d0rmLife

@ d0rmLife: Sie haben genug Erklärungen in den anderen Antworten, die das Gesamtbild abdecken. Ich wollte nur eine mathematisch abstrakte Erklärung als eine andere Sichtweise geben. Auch IMHO würde es weniger Verwirrung beim Aufrufen &als "Adresse von" verursachen, da dies eher an ein Objekt als an den Zeiger an sich gebunden ist "
Abhijit

Nichts für ungut, aber ich werde selbst entscheiden, was eine ausreichende Erklärung ist. Ein Lehrbuch reicht nicht aus, um Datenstrukturen und Speicherzuordnung vollständig zu erklären. ;) .... wie auch immer, deine Antwort ist immer noch hilfreich , auch wenn sie nicht neu ist.
d0rmLife

Es macht keinen Sinn, Zeiger ohne das Konzept des Gedächtnisses zu behandeln . Wenn das Objekt ohne Speicher existiert, muss es sich an einem Ort befinden, an dem keine Adresse vorhanden ist - z. B. in Registern. Die Verwendung von '&' setzt Speicher voraus.
Aki Suihkonen

3

Eine Adresse wird verwendet, um ein Stück Speicher mit fester Größe, normalerweise für jedes Byte, als Ganzzahl zu identifizieren. Dies wird genau als Byteadresse bezeichnet , die auch von der ISO C verwendet wird. Es kann einige andere Methoden geben, um eine Adresse zu erstellen, z. B. für jedes Bit. Es wird jedoch so oft nur die Byteadresse verwendet, dass wir normalerweise "Byte" weglassen.

Technisch gesehen ist eine Adresse niemals ein Wert in C, da die Definition des Begriffs "Wert" in (ISO) C lautet:

genaue Bedeutung des Inhalts eines Objekts, wenn es als ein bestimmter Typ interpretiert wird

(Von mir hervorgehoben.) Es gibt jedoch keinen solchen "Adresstyp" in C.

Zeiger ist nicht dasselbe. Zeiger ist eine Art Typ in der C-Sprache. Es gibt verschiedene Zeigertypen. Sie führt nicht zwangsläufig zu identischem Satz von Regeln der Sprache gehorcht, zum Beispiel der Wirkung ++auf einem Wert vom Typ int*vs. char*.

Ein Wert in C kann vom Zeigertyp sein. Dies wird als Zeigerwert bezeichnet . Ein Zeigerwert ist in der C-Sprache kein Zeiger. Wir sind es jedoch gewohnt, sie miteinander zu mischen, da es in C wahrscheinlich nicht mehrdeutig ist: Wenn wir einen Ausdruck pals "Zeiger" bezeichnen, ist er lediglich ein Zeigerwert, aber kein Typ, da ein benannter Typ in C dies nicht ist durch einen ausgedrückt Ausdruck , sondern durch eine Typname oder ein typedef-Namen .

Einige andere Dinge sind subtil. Als C-Benutzer sollte man zunächst wissen, was objectbedeutet:

Bereich der Datenspeicherung in der Ausführungsumgebung, deren Inhalt Werte darstellen kann

Ein Objekt ist eine Entität zur Darstellung von Werten eines bestimmten Typs. Ein Zeiger ist ein Objekttyp . Wenn wir also deklarieren int* p;, pbedeutet dies "ein Objekt vom Zeigertyp" oder ein "Zeigerobjekt".

Beachten Sie, dass es keine "Variable" gibt, die normativ durch den Standard definiert ist (tatsächlich wird sie von ISO C im normativen Text niemals als Substantiv verwendet). Informell nennen wir ein Objekt jedoch eine Variable, wie es eine andere Sprache tut. (Aber immer noch nicht so genau, z. B. in C ++ kann eine Variable normativ vom Referenztyp sein , was kein Objekt ist.) Die Ausdrücke "Zeigerobjekt" oder "Zeigervariable" werden manchmal wie oben als "Zeigerwert" behandelt, mit a wahrscheinlich geringfügiger Unterschied. (Eine weitere Reihe von Beispielen ist "Array".)

Da der Zeiger ein Typ ist und die Adresse in C effektiv "typenlos" ist, "enthält" ein Zeigerwert ungefähr eine Adresse. Und ein Ausdruck des Zeigertyps kann ergeben eine Adresse, zB

ISO C11 6.5.2.3

3 Der unäre &Operator gibt die Adresse seines Operanden an.

Beachten Sie, dass dieser Wortlaut durch WG14 / N1256, dh ISO C99: TC3, eingeführt wird. In C99 gibt es

3 Der unäre &Operator gibt die Adresse seines Operanden zurück.

Es spiegelt die Meinung des Ausschusses wider: Eine Adresse ist kein Zeigerwert, der vom unären &Operator zurückgegeben wird.

Trotz des obigen Wortlauts gibt es auch in den Standards noch einige Unordnung.

ISO C11 6.6

9 Eine Adresskonstante ist ein Nullzeiger, ein Zeiger auf einen Wert, der ein Objekt mit statischer Speicherdauer angibt, oder ein Zeiger auf einen Funktionsbezeichner

ISO C ++ 11 5.19

3 ... Ein Adresskonstantenausdruck ist ein prvalue-Kernkonstantenausdruck vom Zeigertyp, der die Adresse eines Objekts mit statischer Speicherdauer, die Adresse einer Funktion oder einen Nullzeigerwert oder einen prvalue-Kernkonstantenausdruck ergibt vom Typ std::nullptr_t. ...

(Der aktuelle C ++ - Standardentwurf verwendet einen anderen Wortlaut, sodass dieses Problem nicht auftritt.)

Tatsächlich sind sowohl "Adresskonstante" in C als auch "Adresskonstantenausdruck" in C ++ konstante Ausdrücke von Zeigertypen (oder zumindest "zeigerähnliche" Typen seit C ++ 11).

Und der eingebaute unäre &Operator wird in C und C ++ als "Adresse von" bezeichnet. ähnlich std::addressofwird in C ++ 11 eingeführt.

Diese Benennung kann zu Missverständnissen führen. Der resultierende Ausdruck ist von Zeigertyp, so dass sie als interpretiert werden würde: Das Ergebnis enthält / ergibt sich eine Adresse, anstatt ist eine Adresse.


2

Es heißt "weil es diejenigen verwirrt, die nicht wissen, worum es bei Adressen geht" - es ist auch wahr: Wenn Sie erfahren, worum es bei Adressen geht, werden Sie nicht verwirrt sein. Theoretisch ist der Zeiger eine Variable, die auf eine andere zeigt und praktisch eine Adresse enthält, die die Adresse der Variablen ist, auf die er zeigt. Ich weiß nicht, warum ich diese Tatsache verbergen sollte , es ist keine Raketenwissenschaft. Wenn Sie Zeiger verstehen, sind Sie der Funktionsweise von Computern einen Schritt näher gekommen. Gehen Sie geradeaus!


2

Kommen Sie, um darüber nachzudenken, ich denke, es ist eine Frage der Semantik. Ich denke nicht, dass der Autor Recht hat, da der C-Standard einen Zeiger als eine Adresse für das referenzierte Objekt bezeichnet, wie andere hier bereits erwähnt haben. Adresse! = Speicheradresse. Eine Adresse kann wirklich alles gemäß C-Standard sein, obwohl sie letztendlich zu einer Speicheradresse führt. Der Zeiger selbst kann eine ID, ein Offset + Selektor (x86) sein, wirklich alles, solange er (nach der Zuordnung) einen Speicher beschreiben kann Adresse im adressierbaren Raum.


Ein Zeiger enthält eine Adresse (oder nicht, wenn sie null ist). Das ist jedoch weit davon entfernt , eine Adresse zu sein: Beispielsweise sind zwei Zeiger auf dieselbe Adresse, jedoch mit einem anderen Typ, in vielen Situationen nicht gleichwertig.
Gilles 'SO - hör auf böse zu sein'

@ Gilles Wenn Sie "Sein" sehen, wie in int i=5-> i ist 5, dann ist der Zeiger die Adresse ja. Außerdem hat null auch eine Adresse. Normalerweise eine ungültige Schreibadresse (aber nicht unbedingt, siehe x86-Real-Modus), aber dennoch eine Adresse. Es gibt wirklich nur zwei Anforderungen für Null: Es wird garantiert, dass ein Zeiger ungleich mit einem tatsächlichen Objekt verglichen wird, und zwei beliebige Nullzeiger werden gleich verglichen.
Valentin Radu

Im Gegenteil, es wird garantiert, dass ein Nullzeiger nicht der Adresse eines Objekts entspricht. Das Dereferenzieren eines Nullzeigers ist ein undefiniertes Verhalten. Ein großes Problem bei der Aussage, dass „der Zeiger die Adresse ist“, ist, dass sie unterschiedlich funktionieren. Wenn pes sich um einen Zeiger handelt, p+1wird die Adresse nicht immer um 1 erhöht.
Gilles 'SO - hör auf böse zu sein'

Lesen Sie den Kommentar bitte noch einmal it's guaranteed to compare unequal to a pointer to an actual object. Was die Zeigerarithmetik betrifft, sehe ich den Punkt nicht, der Wert des Zeigers ist immer noch eine Adresse, auch wenn die "+" - Operation nicht unbedingt ein Byte hinzufügen wird.
Valentin Radu

1

Eine andere Art und Weise, in der sich ein C- oder C ++ - Zeiger von einer einfachen Speicheradresse unterscheidet, ist auf die unterschiedlichen Zeigertypen zurückzuführen, die ich in den anderen Antworten nicht gesehen habe (obwohl ich sie aufgrund ihrer Gesamtgröße möglicherweise übersehen habe). Aber es ist wahrscheinlich das wichtigste, weil selbst erfahrene C / C ++ - Programmierer darüber stolpern können:

Der Compiler kann davon ausgehen, dass Zeiger inkompatibler Typen nicht auf dieselbe Adresse verweisen, auch wenn dies eindeutig der Fall ist. Dies kann zu einem Verhalten führen, das mit einem einfachen Zeiger == Adressmodell nicht möglich wäre. Betrachten Sie den folgenden Code (vorausgesetzt sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Beachten Sie, dass es eine Ausnahme für gibt char*, sodass das Bearbeiten von Werten mit char*möglich ist (obwohl dies nicht sehr portabel ist).


0

Kurzzusammenfassung: Die AC-Adresse ist ein Wert, der normalerweise als Speicheradresse auf Maschinenebene mit einem bestimmten Typ dargestellt wird.

Das unqualifizierte Wort "Zeiger" ist mehrdeutig. C hat Zeiger Objekte (Variablen), Zeigertypen , Zeiger Ausdrücke und Zeigerwerte .

Es ist sehr üblich, das Wort "Zeiger" zu verwenden, um "Zeigerobjekt" zu bedeuten, und das kann zu Verwirrung führen. Deshalb versuche ich, "Zeiger" eher als Adjektiv als als Substantiv zu verwenden.

Der C-Standard verwendet zumindest in einigen Fällen das Wort "Zeiger", um "Zeigerwert" zu bedeuten. In der Beschreibung von malloc heißt es beispielsweise, dass "entweder ein Nullzeiger oder ein Zeiger auf den zugewiesenen Speicherplatz zurückgegeben wird".

Was ist eine Adresse in C? Es ist ein Zeigerwert, dh ein Wert eines bestimmten Zeigertyps. (Außer dass ein Nullzeigerwert nicht unbedingt als "Adresse" bezeichnet wird, da er nicht die Adresse von irgendetwas ist).

Die Standardbeschreibung des unären &Operators besagt, dass er "die Adresse seines Operanden liefert". Außerhalb des C-Standards wird das Wort "Adresse" üblicherweise verwendet, um sich auf eine (physische oder virtuelle) Speicheradresse zu beziehen, die typischerweise ein Wort groß ist (unabhängig davon, welches "Wort" sich auf einem bestimmten System befindet).

Eine "AC-Adresse" wird typischerweise als Maschinenadresse implementiert - genau wie ein C- intWert typischerweise als Maschinenwort implementiert wird. Eine C-Adresse (Zeigerwert) ist jedoch mehr als nur eine Maschinenadresse. Es ist ein Wert in der Regel dargestellt als Maschinenadresse, und es ist ein Wert mit einem spezifischen Typ .


0

Ein Zeigerwert ist eine Adresse. Eine Zeigervariable ist ein Objekt, das eine Adresse speichern kann. Dies ist wahr, weil der Standard einen Zeiger so definiert. Es ist wichtig, es C-Neulingen mitzuteilen, da C-Neulinge häufig nicht über den Unterschied zwischen einem Zeiger und dem Objekt informiert sind (das heißt, sie kennen den Unterschied zwischen einer Hülle und einem Gebäude nicht). Der Begriff einer Adresse (jedes Objekt hat eine Adresse und das speichert ein Zeiger) ist wichtig, weil er dies aussortiert.

Der Standard spricht jedoch auf einer bestimmten Abstraktionsebene. Die Personen, über die der Autor spricht, die "wissen, worum es bei Adressen geht", die jedoch neu in C sind, müssen unbedingt über Adressen auf einer anderen Abstraktionsebene informiert sein - möglicherweise durch Programmieren der Assemblersprache. Es gibt keine Garantie dafür, dass die C-Implementierung für Adressen dieselbe Darstellung verwendet wie die CPU-Opcodes (in dieser Passage als "Speicheradresse" bezeichnet), über die diese Personen bereits Bescheid wissen.

Er spricht weiter über "vollkommen vernünftige Adressmanipulation". Was den C-Standard betrifft, gibt es grundsätzlich keine "vollkommen vernünftige Adressmanipulation". Addition wird auf Zeigern definiert und das ist es im Grunde. Natürlich können Sie einen Zeiger in eine Ganzzahl konvertieren, einige bitweise oder arithmetische Operationen ausführen und ihn dann zurückkonvertieren. Dies funktioniert standardmäßig nicht garantiert. Bevor Sie diesen Code schreiben, sollten Sie wissen, wie Ihre bestimmte C-Implementierung Zeiger darstellt und diese Konvertierung durchführt. Es wird wahrscheinlich die von Ihnen erwartete Adressdarstellung verwendet, aber es ist nicht Ihre Schuld, weil Sie das Handbuch nicht gelesen haben. Das ist keine Verwirrung, es ist eine falsche Programmierprozedur ;-)

Kurz gesagt, C verwendet ein abstrakteres Konzept einer Adresse als der Autor.

Das Konzept des Autors einer Adresse ist natürlich auch nicht das niedrigste Wort in dieser Angelegenheit. Was bei virtuellen Speicherzuordnungen und physischer RAM-Adressierung über mehrere Chips hinweg die Nummer, auf die Sie der CPU mitteilen, "die Speicheradresse" ist, auf die Sie zugreifen möchten, hat im Grunde nichts damit zu tun, wo sich die gewünschten Daten tatsächlich in der Hardware befinden. Es sind alles Ebenen der Indirektion und Repräsentation, aber der Autor hat eine als Privileg ausgewählt. Wenn Sie dies tun, wenn Sie über C sprechen, wählen Sie die C-Stufe als Privileg !

Persönlich denke ich nicht, dass die Bemerkungen des Autors allzu hilfreich sind, außer im Zusammenhang mit der Einführung von C in Assembly-Programmierer. Für diejenigen, die aus höheren Sprachen kommen, ist es sicherlich nicht hilfreich zu sagen, dass Zeigerwerte keine Adressen sind. Es wäre weitaus besser, die Komplexität anzuerkennen, als zu sagen, dass die CPU das Monopol hat, zu sagen, was eine Adresse ist, und dass daher C-Zeigerwerte "keine" Adressen sind. Sie sind Adressen, aber sie können in einer anderen Sprache geschrieben sein als die Adressen, die er meint. Ich denke, es wäre angemessen, die beiden Dinge im Kontext von C als "Adresse" und "Geschäftsadresse" zu unterscheiden.


0

Einfach gesagt, Zeiger sind tatsächlich versetzte Teile des Segmentierungsmechanismus, die nach der Segmentierung in eine lineare Adresse und nach dem Paging in eine physikalische Adresse übersetzt werden. Physische Adressen werden tatsächlich von Ihrem RAM adressiert.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.